From 221b24860f81e3b5d5623a52e6993111ba66fdfa Mon Sep 17 00:00:00 2001 From: Robert Rudduck Date: Mon, 20 Feb 2017 11:33:24 -0600 Subject: [PATCH 1/5] Add support for vmss extensions. --- .../resource_arm_virtual_machine_scale_set.go | 154 +++++++++++++++++- ...urce_arm_virtual_machine_scale_set_test.go | 150 +++++++++++++++++ 2 files changed, 301 insertions(+), 3 deletions(-) diff --git a/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set.go b/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set.go index 02389f6707c0..bc6079cf23f8 100644 --- a/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set.go +++ b/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set.go @@ -341,6 +341,55 @@ func resourceArmVirtualMachineScaleSet() *schema.Resource { Set: resourceArmVirtualMachineScaleSetStorageProfileImageReferenceHash, }, + "extension": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "publisher": { + Type: schema.TypeString, + Required: true, + }, + + "type": { + Type: schema.TypeString, + Required: true, + }, + + "type_handler_version": { + Type: schema.TypeString, + Required: true, + }, + + "auto_upgrade_minor_version": { + Type: schema.TypeBool, + Optional: true, + }, + + "settings": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateJsonString, + DiffSuppressFunc: suppressDiffVirtualMachineExtensionSettings, + }, + + "protected_settings": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validateJsonString, + DiffSuppressFunc: suppressDiffVirtualMachineExtensionSettings, + }, + }, + }, + Set: resourceArmVirtualMachineScaleSetExtensionHash, + }, + "tags": tagsSchema(), }, } @@ -381,6 +430,11 @@ func resourceArmVirtualMachineScaleSetCreate(d *schema.ResourceData, meta interf return err } + extensions, err := expandAzureRMVirtualMachineScaleSetExtensions(d) + if err != nil { + return err + } + updatePolicy := d.Get("upgrade_policy_mode").(string) overprovision := d.Get("overprovision").(bool) scaleSetProps := compute.VirtualMachineScaleSetProperties{ @@ -388,9 +442,10 @@ func resourceArmVirtualMachineScaleSetCreate(d *schema.ResourceData, meta interf Mode: compute.UpgradeMode(updatePolicy), }, VirtualMachineProfile: &compute.VirtualMachineScaleSetVMProfile{ - NetworkProfile: expandAzureRmVirtualMachineScaleSetNetworkProfile(d), - StorageProfile: &storageProfile, - OsProfile: osProfile, + NetworkProfile: expandAzureRmVirtualMachineScaleSetNetworkProfile(d), + StorageProfile: &storageProfile, + OsProfile: osProfile, + ExtensionProfile: extensions, }, Overprovision: &overprovision, } @@ -488,6 +543,12 @@ func resourceArmVirtualMachineScaleSetRead(d *schema.ResourceData, meta interfac return fmt.Errorf("[DEBUG] Error setting Virtual Machine Scale Set Storage Profile OS Disk error: %#v", err) } + if properties.VirtualMachineProfile.ExtensionProfile != nil { + if err := d.Set("extension", flattenAzureRmVirtualMachineScaleSetExtensionProfile(properties.VirtualMachineProfile.ExtensionProfile)); err != nil { + return fmt.Errorf("[DEBUG] Error setting Virtual Machine Scale Set Extension Profile error: %#v", err) + } + } + flattenAndSetTags(d, resp.Tags) return nil @@ -709,6 +770,38 @@ func flattenAzureRmVirtualMachineScaleSetSku(sku *compute.Sku) []interface{} { return []interface{}{result} } +func flattenAzureRmVirtualMachineScaleSetExtensionProfile(profile *compute.VirtualMachineScaleSetExtensionProfile) []map[string]interface{} { + if profile.Extensions == nil { + return nil + } + + result := make([]map[string]interface{}, 0, len(*profile.Extensions)) + for _, extension := range *profile.Extensions { + e := make(map[string]interface{}) + e["name"] = *extension.Name + properties := extension.VirtualMachineScaleSetExtensionProperties + if properties != nil { + e["publisher"] = *properties.Publisher + e["type"] = *properties.Type + e["type_handler_version"] = *properties.TypeHandlerVersion + if properties.AutoUpgradeMinorVersion != nil { + e["auto_upgrade_minor_version"] = *properties.AutoUpgradeMinorVersion + } + + if properties.Settings != nil { + settings, err := flattenArmVirtualMachineExtensionSettings(*properties.Settings) + if err == nil { + e["settings"] = settings + } + } + } + + result = append(result, e) + } + + return result +} + func resourceArmVirtualMachineScaleSetStorageProfileImageReferenceHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) @@ -783,6 +876,13 @@ func resourceArmVirtualMachineScaleSetOsProfileLWindowsConfigHash(v interface{}) return hashcode.String(buf.String()) } +func resourceArmVirtualMachineScaleSetExtensionHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s", m["name"].(string))) + return hashcode.String(buf.String()) +} + func expandVirtualMachineScaleSetSku(d *schema.ResourceData) (*compute.Sku, error) { skuConfig := d.Get("sku").(*schema.Set).List() @@ -1111,3 +1211,51 @@ func expandAzureRmVirtualMachineScaleSetOsProfileSecrets(d *schema.ResourceData) return &secrets } + +func expandAzureRMVirtualMachineScaleSetExtensions(d *schema.ResourceData) (*compute.VirtualMachineScaleSetExtensionProfile, error) { + extensions := d.Get("extension").(*schema.Set).List() + resources := make([]compute.VirtualMachineScaleSetExtension, 0, len(extensions)) + for _, e := range extensions { + config := e.(map[string]interface{}) + name := config["name"].(string) + publisher := config["publisher"].(string) + t := config["type"].(string) + version := config["type_handler_version"].(string) + + extension := compute.VirtualMachineScaleSetExtension{ + Name: &name, + VirtualMachineScaleSetExtensionProperties: &compute.VirtualMachineScaleSetExtensionProperties{ + Publisher: &publisher, + Type: &t, + TypeHandlerVersion: &version, + }, + } + + if u := config["auto_upgrade_minor_version"]; u != nil { + upgrade := u.(bool) + extension.VirtualMachineScaleSetExtensionProperties.AutoUpgradeMinorVersion = &upgrade + } + + if s := config["settings"].(string); s != "" { + settings, err := expandArmVirtualMachineExtensionSettings(s) + if err != nil { + return nil, fmt.Errorf("unable to parse settings: %s", err) + } + extension.VirtualMachineScaleSetExtensionProperties.Settings = &settings + } + + if s := config["protected_settings"].(string); s != "" { + protectedSettings, err := expandArmVirtualMachineExtensionSettings(s) + if err != nil { + return nil, fmt.Errorf("unable to parse protected_settings: %s", err) + } + extension.VirtualMachineScaleSetExtensionProperties.ProtectedSettings = &protectedSettings + } + + resources = append(resources, extension) + } + + return &compute.VirtualMachineScaleSetExtensionProfile{ + Extensions: &resources, + }, nil +} diff --git a/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set_test.go b/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set_test.go index 8b5c874d10fa..07ea7ec653b1 100644 --- a/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set_test.go +++ b/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set_test.go @@ -86,6 +86,25 @@ func TestAccAzureRMVirtualMachineScaleSet_overprovision(t *testing.T) { }) } +func TestAccAzureRMVirtualMachineScaleSet_extension(t *testing.T) { + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAzureRMVirtualMachineScaleSetExtensionTemplate, ri, ri, ri, ri, ri, ri) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualMachineScaleSetDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualMachineScaleSetExists("azurerm_virtual_machine_scale_set.test"), + testCheckAzureRMVirtualMachineScaleSetExtension("azurerm_virtual_machine_scale_set.test"), + ), + }, + }, + }) +} + func testCheckAzureRMVirtualMachineScaleSetExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { // Ensure we have enough information in state to look up in API @@ -240,6 +259,39 @@ func testCheckAzureRMVirtualMachineScaleSetOverprovision(name string) resource.T } } +func testCheckAzureRMVirtualMachineScaleSetExtension(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + name := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for virtual machine: scale set %s", name) + } + + conn := testAccProvider.Meta().(*ArmClient).vmScaleSetClient + resp, err := conn.Get(resourceGroup, name) + if err != nil { + return fmt.Errorf("Bad: Get on vmScaleSetClient: %s", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: VirtualMachineScaleSet %q (resource group: %q) does not exist", name, resourceGroup) + } + + n := resp.VirtualMachineProfile.ExtensionProfile.Extensions + if n == nil || len(*n) == 0 { + return fmt.Errorf("Bad: Could not get extensions for scale set %v", name) + } + + return nil + } +} + var testAccAzureRMVirtualMachineScaleSet_basicLinux = ` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" @@ -507,3 +559,101 @@ resource "azurerm_virtual_machine_scale_set" "test" { } } ` + +var testAccAzureRMVirtualMachineScaleSetExtensionTemplate = ` +resource "azurerm_resource_group" "test" { + name = "acctestrg-%d" + location = "southcentralus" +} + +resource "azurerm_virtual_network" "test" { + name = "acctvn-%d" + address_space = ["10.0.0.0/16"] + location = "southcentralus" + 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 = "southcentralus" + account_type = "Standard_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 = "southcentralus" + resource_group_name = "${azurerm_resource_group.test.name}" + upgrade_policy_mode = "Manual" + overprovision = false + + sku { + name = "Standard_A0" + tier = "Standard" + capacity = 1 + } + + 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 = "14.04.2-LTS" + version = "latest" + } + + extension { + name = "CustomScript" + publisher = "Microsoft.Azure.Extensions" + type = "CustomScript" + type_handler_version = "2.0" + auto_upgrade_minor_version = true + settings = < Date: Mon, 20 Feb 2017 16:38:24 -0600 Subject: [PATCH 2/5] Update website. --- .../r/virtual_machine_scale_sets.html.markdown | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/website/source/docs/providers/azurerm/r/virtual_machine_scale_sets.html.markdown b/website/source/docs/providers/azurerm/r/virtual_machine_scale_sets.html.markdown index fe30e7b90ac8..adfe9dd76960 100644 --- a/website/source/docs/providers/azurerm/r/virtual_machine_scale_sets.html.markdown +++ b/website/source/docs/providers/azurerm/r/virtual_machine_scale_sets.html.markdown @@ -122,6 +122,7 @@ The following arguments are supported: * `network_profile` - (Required) A collection of network profile block as documented below. * `storage_profile_os_disk` - (Required) A storage profile os disk block as documented below * `storage_profile_image_reference` - (Optional) A storage profile image reference block as documented below. +* `extension` - (Optional) Can be specified multiple times to add extension profiles to the scale set. Each `extension` block supports the fields documented below. * `tags` - (Optional) A mapping of tags to assign to the resource. @@ -206,6 +207,16 @@ The following arguments are supported: * `sku` - (Required) Specifies the SKU of the image used to create the virtual machines. * `version` - (Optional) Specifies the version of the image used to create the virtual machines. +`extension` supports the following: + +* `name` - (Required) Specifies the name of the extension. +* `publisher` - (Required) The publisher of the extension, available publishers can be found by using the Azure CLI. +* `type` - (Required) The type of extension, available types for a publisher can be found using the Azure CLI. +* `type_handler_version` - (Required) Specifies the version of the extension to use, available versions can be found using the Azure CLI. +* `auto_upgrade_minor_version` - (Optional) Specifies whether or not to use the latest minor version available. +* `settings` - (Required) The settings passed to the extension, these are specified as a JSON object in a string. +* `protected_settings` - (Optional) The protected_settings passed to the extension, like settings, these are specified as a JSON object in a string. + ## Attributes Reference The following attributes are exported: From 0ca483f82c9a4af0554f4906ae531590c64c7b0c Mon Sep 17 00:00:00 2001 From: Robert Rudduck Date: Sun, 26 Feb 2017 20:42:46 -0600 Subject: [PATCH 3/5] Add multi extension test. --- ...urce_arm_virtual_machine_scale_set_test.go | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set_test.go b/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set_test.go index 07ea7ec653b1..e9f3d1ef9cf1 100644 --- a/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set_test.go +++ b/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set_test.go @@ -105,6 +105,25 @@ func TestAccAzureRMVirtualMachineScaleSet_extension(t *testing.T) { }) } +func TestAccAzureRMVirtualMachineScaleSet_multipleExtensions(t *testing.T) { + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAzureRMVirtualMachineScaleSetMultipleExtensionsTemplate, ri, ri, ri, ri, ri, ri) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualMachineScaleSetDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualMachineScaleSetExists("azurerm_virtual_machine_scale_set.test"), + testCheckAzureRMVirtualMachineScaleSetExtension("azurerm_virtual_machine_scale_set.test"), + ), + }, + }, + }) +} + func testCheckAzureRMVirtualMachineScaleSetExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { // Ensure we have enough information in state to look up in API @@ -657,3 +676,109 @@ SETTINGS } } ` + +var testAccAzureRMVirtualMachineScaleSetMultipleExtensionsTemplate = ` +resource "azurerm_resource_group" "test" { + name = "acctestrg-%d" + location = "southcentralus" +} + +resource "azurerm_virtual_network" "test" { + name = "acctvn-%d" + address_space = ["10.0.0.0/16"] + location = "southcentralus" + 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 = "southcentralus" + account_type = "Standard_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 = "southcentralus" + resource_group_name = "${azurerm_resource_group.test.name}" + upgrade_policy_mode = "Manual" + overprovision = false + + sku { + name = "Standard_A0" + tier = "Standard" + capacity = 1 + } + + 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 = "14.04.2-LTS" + version = "latest" + } + + extension { + name = "CustomScript" + publisher = "Microsoft.Azure.Extensions" + type = "CustomScript" + type_handler_version = "2.0" + auto_upgrade_minor_version = true + settings = < Date: Thu, 9 Mar 2017 16:40:21 -0600 Subject: [PATCH 4/5] Return error from settings parsing. --- .../resource_arm_virtual_machine_scale_set.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set.go b/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set.go index bc6079cf23f8..e38d97bd5d51 100644 --- a/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set.go +++ b/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set.go @@ -544,9 +544,11 @@ func resourceArmVirtualMachineScaleSetRead(d *schema.ResourceData, meta interfac } if properties.VirtualMachineProfile.ExtensionProfile != nil { - if err := d.Set("extension", flattenAzureRmVirtualMachineScaleSetExtensionProfile(properties.VirtualMachineProfile.ExtensionProfile)); err != nil { + extension, err := flattenAzureRmVirtualMachineScaleSetExtensionProfile(properties.VirtualMachineProfile.ExtensionProfile) + if err != nil { return fmt.Errorf("[DEBUG] Error setting Virtual Machine Scale Set Extension Profile error: %#v", err) } + d.Set("extension", extension) } flattenAndSetTags(d, resp.Tags) @@ -770,9 +772,9 @@ func flattenAzureRmVirtualMachineScaleSetSku(sku *compute.Sku) []interface{} { return []interface{}{result} } -func flattenAzureRmVirtualMachineScaleSetExtensionProfile(profile *compute.VirtualMachineScaleSetExtensionProfile) []map[string]interface{} { +func flattenAzureRmVirtualMachineScaleSetExtensionProfile(profile *compute.VirtualMachineScaleSetExtensionProfile) ([]map[string]interface{}, error) { if profile.Extensions == nil { - return nil + return nil, nil } result := make([]map[string]interface{}, 0, len(*profile.Extensions)) @@ -790,16 +792,17 @@ func flattenAzureRmVirtualMachineScaleSetExtensionProfile(profile *compute.Virtu if properties.Settings != nil { settings, err := flattenArmVirtualMachineExtensionSettings(*properties.Settings) - if err == nil { - e["settings"] = settings + if err != nil { + return nil, err } + e["settings"] = settings } } result = append(result, e) } - return result + return result, nil } func resourceArmVirtualMachineScaleSetStorageProfileImageReferenceHash(v interface{}) int { From bf6676e98106173d2781e99f9645886f0477902e Mon Sep 17 00:00:00 2001 From: Robert Rudduck Date: Thu, 9 Mar 2017 16:53:10 -0600 Subject: [PATCH 5/5] Update extension hash. --- .../azurerm/resource_arm_virtual_machine_scale_set.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set.go b/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set.go index e38d97bd5d51..593e08ff8427 100644 --- a/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set.go +++ b/builtin/providers/azurerm/resource_arm_virtual_machine_scale_set.go @@ -882,7 +882,14 @@ func resourceArmVirtualMachineScaleSetOsProfileLWindowsConfigHash(v interface{}) func resourceArmVirtualMachineScaleSetExtensionHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s", m["name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["publisher"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["type"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["type_handler_version"].(string))) + if m["auto_upgrade_minor_version"] != nil { + buf.WriteString(fmt.Sprintf("%t-", m["auto_upgrade_minor_version"].(bool))) + } + return hashcode.String(buf.String()) }