Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Managed Service Identity (MSI) support to VM Scale Sets #1018

Merged
merged 10 commits into from
Apr 4, 2018
53 changes: 53 additions & 0 deletions azurerm/resource_arm_virtual_machine_scale_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,29 @@ func resourceArmVirtualMachineScaleSet() *schema.Resource {

"zones": zonesSchema(),

"identity": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add an acceptance test covering this use-case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Type: schema.TypeString,
Required: true,
DiffSuppressFunc: ignoreCaseDiffSuppressFunc,
ValidateFunc: validation.StringInSlice([]string{
"SystemAssigned",
}, true),
},
"principal_id": {
Type: schema.TypeString,
Computed: true,
},
},
},
},

"sku": {
Type: schema.TypeSet,
Required: true,
Expand Down Expand Up @@ -639,6 +662,10 @@ func resourceArmVirtualMachineScaleSetCreate(d *schema.ResourceData, meta interf
Zones: zones,
}

if _, ok := d.GetOk("identity"); ok {
scaleSetParams.Identity = expandAzureRmVirtualMachineScaleSetIdentity(d)
}

if _, ok := d.GetOk("plan"); ok {
plan, err := expandAzureRmVirtualMachineScaleSetPlan(d)
if err != nil {
Expand Down Expand Up @@ -701,6 +728,8 @@ func resourceArmVirtualMachineScaleSetRead(d *schema.ResourceData, meta interfac
return fmt.Errorf("[DEBUG] Error setting Virtual Machine Scale Set Sku error: %#v", err)
}

d.Set("identity", flattenAzureRmVirtualMachineScaleSetIdentity(resp.Identity))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we change this to:

if err := d.Set("identity", flattenAzureRmVirtualMachineScaleSetIdentity(resp.Identity)); err != nil {
  return fmt.Error("Error flattening `identity`: %+v", err)
}

this allows any bugs (such as the schema not matching) to be caught

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


properties := resp.VirtualMachineScaleSetProperties

d.Set("upgrade_policy_mode", properties.UpgradePolicy.Mode)
Expand Down Expand Up @@ -803,6 +832,20 @@ func resourceArmVirtualMachineScaleSetDelete(d *schema.ResourceData, meta interf
return nil
}

func flattenAzureRmVirtualMachineScaleSetIdentity(identity *compute.VirtualMachineScaleSetIdentity) []interface{} {
if identity == nil {
return make([]interface{}, 0)
}

result := make(map[string]interface{})
result["type"] = string(identity.Type)
if identity.PrincipalID != nil {
result["principal_id"] = *identity.PrincipalID
}

return []interface{}{result}
}

func flattenAzureRmVirtualMachineScaleSetOsProfileLinuxConfig(config *compute.LinuxConfiguration) []interface{} {
result := make(map[string]interface{})
result["disable_password_authentication"] = *config.DisablePasswordAuthentication
Expand Down Expand Up @@ -1422,6 +1465,16 @@ func expandAzureRMVirtualMachineScaleSetsDiagnosticProfile(d *schema.ResourceDat
return diagnosticsProfile
}

func expandAzureRmVirtualMachineScaleSetIdentity(d *schema.ResourceData) *compute.VirtualMachineScaleSetIdentity {
v := d.Get("identity")
identities := v.([]interface{})
identity := identities[0].(map[string]interface{})
identityType := identity["type"].(string)
return &compute.VirtualMachineScaleSetIdentity{
Type: compute.ResourceIdentityType(identityType),
}
}

func expandAzureRMVirtualMachineScaleSetsStorageProfileOsDisk(d *schema.ResourceData) (*compute.VirtualMachineScaleSetOSDisk, error) {
osDiskConfigs := d.Get("storage_profile_os_disk").(*schema.Set).List()

Expand Down
138 changes: 137 additions & 1 deletion azurerm/resource_arm_virtual_machine_scale_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,26 @@ func TestAccAzureRMVirtualMachineScaleSet_overprovision(t *testing.T) {
})
}

func TestAccAzureRMVirtualMachineScaleSet_MSI(t *testing.T) {
resourceName := "azurerm-vmss-msi-test"
ri := acctest.RandInt()
config := testAccAzureRMVirtualMachineScaleSetMSITemplate(ri, testLocation())
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMVirtualMachineScaleSetDestroy,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMVirtualMachineScaleSetExists(resourceName),
testCheckAzureRMVirtualMachineScaleSetMSI(resourceName),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we remove this function (which checks the API) in favour of checking the value stored in the state (which users will consume)? we can do this via:

resources.TestCheckResourceAttrSet(resourceName, "identity.0.principal_id"),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

),
},
},
})
}

func TestAccAzureRMVirtualMachineScaleSet_extension(t *testing.T) {
ri := acctest.RandInt()
config := testAccAzureRMVirtualMachineScaleSetExtensionTemplate(ri, testLocation())
Expand Down Expand Up @@ -765,6 +785,27 @@ func testCheckAzureRMVirtualMachineScaleSetSinglePlacementGroup(name string, exp
}
}

func testCheckAzureRMVirtualMachineScaleSetMSI(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resp, err := testGetAzureRMVirtualMachineScaleSet(s, name)
if err != nil {
return err
}

identityType := resp.Identity.Type
if identityType != "systemAssigned" {
return fmt.Errorf("Bad: Identity Type is not systemAssigned for scale set %v", name)
}

principalID := *resp.Identity.PrincipalID
if len(principalID) == 0 {
return fmt.Errorf("Bad: Could not get principal_id for scale set %v", name)
}

return nil
}
}

func testCheckAzureRMVirtualMachineScaleSetExtension(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resp, err := testGetAzureRMVirtualMachineScaleSet(s, name)
Expand Down Expand Up @@ -878,7 +919,7 @@ resource "azurerm_virtual_machine_scale_set" "test" {
tier = "Standard"
capacity = 2
}

os_profile {
computer_name_prefix = "testvm-%d"
admin_username = "myadmin"
Expand Down Expand Up @@ -2299,6 +2340,101 @@ resource "azurerm_virtual_machine_scale_set" "test" {
`, rInt, location, rInt, rInt, rInt, rInt, rInt)
}

func testAccAzureRMVirtualMachineScaleSetMSITemplate(rInt int, location string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "test" {
name = "acctestrg-%d"
location = "%s"
}

resource "azurerm_virtual_network" "test" {
name = "acctvn-%d"
address_space = ["10.0.0.0/16"]
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
}

resource "azurerm_subnet" "test" {
name = "acctsub-%d"
resource_group_name = "${azurerm_resource_group.test.name}"
virtual_network_name = "${azurerm_virtual_network.test.name}"
address_prefix = "10.0.2.0/24"
}

resource "azurerm_storage_account" "test" {
name = "accsa%d"
resource_group_name = "${azurerm_resource_group.test.name}"
location = "${azurerm_resource_group.test.location}"
account_tier = "Standard"
account_replication_type = "LRS"
}

resource "azurerm_storage_container" "test" {
name = "vhds"
resource_group_name = "${azurerm_resource_group.test.name}"
storage_account_name = "${azurerm_storage_account.test.name}"
container_access_type = "private"
}

resource "azurerm_virtual_machine_scale_set" "test" {
name = "acctvmss-%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
upgrade_policy_mode = "Manual"
overprovision = false

sku {
name = "Standard_D1_v2"
tier = "Standard"
capacity = 1
}

identity {
type = "systemAssigned"
}

extension {
name = "MSILinuxExtension"
publisher = "Microsoft.ManagedIdentity"
type = "ManagedIdentityExtensionForLinux"
type_handler_version = "1.0"
settings = "{\"port\": 50342}"
}

os_profile {
computer_name_prefix = "testvm-%d"
admin_username = "myadmin"
admin_password = "Passwword1234"
}

network_profile {
name = "TestNetworkProfile"
primary = true

ip_configuration {
name = "TestIPConfiguration"
subnet_id = "${azurerm_subnet.test.id}"
}
}

storage_profile_os_disk {
name = "os-disk"
caching = "ReadWrite"
create_option = "FromImage"
vhd_containers = ["${azurerm_storage_account.test.primary_blob_endpoint}${azurerm_storage_container.test.name}"]
}

storage_profile_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
}

`, rInt, location, rInt, rInt, rInt, rInt, rInt)
}

func testAccAzureRMVirtualMachineScaleSetExtensionTemplate(rInt int, location string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "test" {
Expand Down
33 changes: 33 additions & 0 deletions website/docs/r/virtual_machine_scale_set.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,39 @@ The following arguments are supported:
* `tier` - (Optional) Specifies the tier of virtual machines in a scale set. Possible values, `standard` or `basic`.
* `capacity` - (Required) Specifies the number of virtual machines in the scale set.

`identity` supports the following:

* `type` - (Required) Specifies the identity type to be assigned to the scale set. The only allowable value is `SystemAssigned`. To enable Managed Service Identity (MSI) on all machines in the scale set, an extension with the type "ManagedIdentityExtensionForWindows" or "ManagedIdentityExtensionForLinux" must also be added. The scale set's Service Principal ID (SPN) can be retrieved after the scale set has been created.

```hcl
resource "azurerm_virtual_machine_scale_set" "test" {
name = "vm-scaleset"
resource_group_name = "${azurerm_resource_group.test.name}"
location = "${azurerm_resource_group.test.location}"

sku {
name = "${var.vm_sku}"
tier = "Standard"
capacity = "${var.instance_count}"
}

identity {
type = "systemAssigned"
}

extension {
name = "MSILinuxExtension"
publisher = "Microsoft.ManagedIdentity"
type = "ManagedIdentityExtensionForLinux"
type_handler_version = "1.0"
settings = "{\"port\": 50342}"
}

output "principal_id" {
value = "${lookup(azurerm_virtual_machine.test.identity[0], "principal_id")}"
}
```

`os_profile` supports the following:

* `computer_name_prefix` - (Required) Specifies the computer name prefix for all of the virtual machines in the scale set. Computer name prefixes must be 1 to 9 characters long for windows images and 1 - 58 for linux. Changing this forces a new resource to be created.
Expand Down