From 8a6346ce5e122fdcd18ab4e47415d27d21912c86 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Tue, 14 Nov 2017 16:09:31 +0000 Subject: [PATCH] vmss: Support for updating the customData field Fixes #61 Fixes #490 --- ...port_arm_virtual_machine_scale_set_test.go | 43 ++++-- azurerm/provider.go | 5 + .../resource_arm_virtual_machine_scale_set.go | 45 +++--- ...urce_arm_virtual_machine_scale_set_test.go | 128 ++++++++++++++++++ .../r/virtual_machine_scale_set.html.markdown | 2 +- 5 files changed, 189 insertions(+), 34 deletions(-) diff --git a/azurerm/import_arm_virtual_machine_scale_set_test.go b/azurerm/import_arm_virtual_machine_scale_set_test.go index 71faf5758c8c..edc88a94b915 100644 --- a/azurerm/import_arm_virtual_machine_scale_set_test.go +++ b/azurerm/import_arm_virtual_machine_scale_set_test.go @@ -22,9 +22,10 @@ func TestAccAzureRMVirtualMachineScaleSet_importBasic(t *testing.T) { Config: config, }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"os_profile.0.admin_password"}, }, }, }) @@ -45,9 +46,10 @@ func TestAccAzureRMVirtualMachineScaleSet_importBasic_managedDisk(t *testing.T) Config: config, }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"os_profile.0.admin_password"}, }, }, }) @@ -71,6 +73,10 @@ func TestAccAzureRMVirtualMachineScaleSet_importLinux(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "os_profile.0.admin_password", + "os_profile.0.custom_data", + }, }, }, }) @@ -91,9 +97,10 @@ func TestAccAzureRMVirtualMachineScaleSet_importLoadBalancer(t *testing.T) { Config: config, }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"os_profile.0.admin_password"}, }, }, }) @@ -116,6 +123,12 @@ func TestAccAzureRMVirtualMachineScaleSet_importOverProvision(t *testing.T) { testCheckAzureRMVirtualMachineScaleSetOverprovision(resourceName), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"os_profile.0.admin_password"}, + }, }, }) } @@ -137,6 +150,12 @@ func TestAccAzureRMVirtualMachineScaleSet_importExtension(t *testing.T) { testCheckAzureRMVirtualMachineScaleSetExtension(resourceName), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"os_profile.0.admin_password"}, + }, }, }) } @@ -158,6 +177,12 @@ func TestAccAzureRMVirtualMachineScaleSet_importMultipleExtensions(t *testing.T) testCheckAzureRMVirtualMachineScaleSetExtension(resourceName), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"os_profile.0.admin_password"}, + }, }, }) } diff --git a/azurerm/provider.go b/azurerm/provider.go index eb73ea196a4e..5f7dfea31866 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -494,6 +494,11 @@ func ignoreCaseStateFunc(val interface{}) string { return strings.ToLower(val.(string)) } +func userDataDiffSuppressFunc(k, old, new string, d *schema.ResourceData) bool { + oldValue := userDataStateFunc(old) + return oldValue == new +} + func userDataStateFunc(v interface{}) string { switch s := v.(type) { case string: diff --git a/azurerm/resource_arm_virtual_machine_scale_set.go b/azurerm/resource_arm_virtual_machine_scale_set.go index daf71d642c19..e4c7a35a98c1 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set.go +++ b/azurerm/resource_arm_virtual_machine_scale_set.go @@ -80,7 +80,7 @@ func resourceArmVirtualMachineScaleSet() *schema.Resource { }, "os_profile": { - Type: schema.TypeSet, + Type: schema.TypeList, Required: true, MaxItems: 1, Elem: &schema.Resource{ @@ -102,14 +102,13 @@ func resourceArmVirtualMachineScaleSet() *schema.Resource { }, "custom_data": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - StateFunc: userDataStateFunc, + Type: schema.TypeString, + Optional: true, + StateFunc: userDataStateFunc, + DiffSuppressFunc: userDataDiffSuppressFunc, }, }, }, - Set: resourceArmVirtualMachineScaleSetsOsProfileHash, }, "os_profile_secrets": { @@ -697,7 +696,7 @@ func resourceArmVirtualMachineScaleSetRead(d *schema.ResourceData, meta interfac d.Set("overprovision", properties.Overprovision) d.Set("single_placement_group", properties.SinglePlacementGroup) - osProfile, err := flattenAzureRMVirtualMachineScaleSetOsProfile(properties.VirtualMachineProfile.OsProfile) + osProfile, err := flattenAzureRMVirtualMachineScaleSetOsProfile(d, properties.VirtualMachineProfile.OsProfile) if err != nil { return fmt.Errorf("[DEBUG] Error flattening Virtual Machine Scale Set OS Profile. Error: %#v", err) } @@ -964,14 +963,27 @@ func flattenAzureRmVirtualMachineScaleSetNetworkProfile(profile *compute.Virtual return result } -func flattenAzureRMVirtualMachineScaleSetOsProfile(profile *compute.VirtualMachineScaleSetOSProfile) ([]interface{}, error) { +func flattenAzureRMVirtualMachineScaleSetOsProfile(d *schema.ResourceData, profile *compute.VirtualMachineScaleSetOSProfile) ([]interface{}, error) { result := make(map[string]interface{}) result["computer_name_prefix"] = *profile.ComputerNamePrefix result["admin_username"] = *profile.AdminUsername + // admin password isn't returned, so let's look it up + if v, ok := d.GetOk("os_profile.0.admin_password"); ok { + password := v.(string) + result["admin_password"] = password + } + if profile.CustomData != nil { result["custom_data"] = *profile.CustomData + } else { + // look up the current custom data + value := d.Get("os_profile.0.custom_data").(string) + if !isBase64Encoded(value) { + value = base64Encode(value) + } + result["custom_data"] = value } return []interface{}{result}, nil @@ -1146,21 +1158,6 @@ func resourceArmVirtualMachineScaleSetNetworkConfigurationHash(v interface{}) in return hashcode.String(buf.String()) } -func resourceArmVirtualMachineScaleSetsOsProfileHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["computer_name_prefix"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["admin_username"].(string))) - if m["custom_data"] != nil { - customData := m["custom_data"].(string) - if !isBase64Encoded(customData) { - customData = base64Encode(customData) - } - buf.WriteString(fmt.Sprintf("%s-", customData)) - } - return hashcode.String(buf.String()) -} - func resourceArmVirtualMachineScaleSetOsProfileLinuxConfigHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) @@ -1325,7 +1322,7 @@ func expandAzureRmVirtualMachineScaleSetNetworkProfile(d *schema.ResourceData) * } func expandAzureRMVirtualMachineScaleSetsOsProfile(d *schema.ResourceData) (*compute.VirtualMachineScaleSetOSProfile, error) { - osProfileConfigs := d.Get("os_profile").(*schema.Set).List() + osProfileConfigs := d.Get("os_profile").([]interface{}) osProfileConfig := osProfileConfigs[0].(map[string]interface{}) namePrefix := osProfileConfig["computer_name_prefix"].(string) diff --git a/azurerm/resource_arm_virtual_machine_scale_set_test.go b/azurerm/resource_arm_virtual_machine_scale_set_test.go index 1d0761aff5f7..ffdea5290223 100644 --- a/azurerm/resource_arm_virtual_machine_scale_set_test.go +++ b/azurerm/resource_arm_virtual_machine_scale_set_test.go @@ -181,6 +181,34 @@ func TestAccAzureRMVirtualMachineScaleSet_linuxUpdated(t *testing.T) { }) } +func TestAccAzureRMVirtualMachineScaleSet_customDataUpdated(t *testing.T) { + resourceName := "azurerm_virtual_machine_scale_set.test" + ri := acctest.RandInt() + location := testLocation() + config := testAccAzureRMVirtualMachineScaleSet_linux(ri, location) + updatedConfig := testAccAzureRMVirtualMachineScaleSet_linuxCustomDataUpdated(ri, location) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMVirtualMachineScaleSetDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualMachineScaleSetExists(resourceName), + ), + }, + { + Config: updatedConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMVirtualMachineScaleSetExists(resourceName), + ), + }, + }, + }) +} + func TestAccAzureRMVirtualMachineScaleSet_basicLinux_managedDisk(t *testing.T) { ri := acctest.RandInt() config := testAccAzureRMVirtualMachineScaleSet_basicLinux_managedDisk(ri, testLocation()) @@ -1659,6 +1687,106 @@ resource "azurerm_virtual_machine_scale_set" "test" { `, rInt, location, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt) } +func testAccAzureRMVirtualMachineScaleSet_linuxCustomDataUpdated(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestrg-%d" + location = "%s" +} +resource "azurerm_virtual_network" "test" { + name = "acctestvn-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + address_space = ["10.0.0.0/8"] +} +resource "azurerm_subnet" "test" { + name = "acctestsn-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.1.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 = "acctestsc-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + container_access_type = "private" +} +resource "azurerm_public_ip" "test" { + name = "acctestpip-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + public_ip_address_allocation = "static" +} +resource "azurerm_lb" "test" { + name = "acctestlb-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + frontend_ip_configuration { + name = "ip-address" + public_ip_address_id = "${azurerm_public_ip.test.id}" + } +} +resource "azurerm_lb_backend_address_pool" "test" { + name = "acctestbap-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + loadbalancer_id = "${azurerm_lb.test.id}" +} +resource "azurerm_virtual_machine_scale_set" "test" { + name = "acctestvmss-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + upgrade_policy_mode = "Automatic" + sku { + name = "Standard_A0" + tier = "Standard" + capacity = "1" + } + os_profile { + computer_name_prefix = "prefix" + admin_username = "ubuntu" + admin_password = "password" + custom_data = "updated custom data!" + } + os_profile_linux_config { + disable_password_authentication = true + ssh_keys { + path = "/home/ubuntu/.ssh/authorized_keys" + key_data = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDCsTcryUl51Q2VSEHqDRNmceUFo55ZtcIwxl2QITbN1RREti5ml/VTytC0yeBOvnZA4x4CFpdw/lCDPk0yrH9Ei5vVkXmOrExdTlT3qI7YaAzj1tUVlBd4S6LX1F7y6VLActvdHuDDuXZXzCDd/97420jrDfWZqJMlUK/EmCE5ParCeHIRIvmBxcEnGfFIsw8xQZl0HphxWOtJil8qsUWSdMyCiJYYQpMoMliO99X40AUc4/AlsyPyT5ddbKk08YrZ+rKDVHF7o29rh4vi5MmHkVgVQHKiKybWlHq+b71gIAUQk9wrJxD+dqt4igrmDSpIjfjwnd+l5UIn5fJSO5DYV4YT/4hwK7OKmuo7OFHD0WyY5YnkYEMtFgzemnRBdE8ulcT60DQpVgRMXFWHvhyCWy0L6sgj1QWDZlLpvsIvNfHsyhKFMG1frLnMt/nP0+YCcfg+v1JYeCKjeoJxB8DWcRBsjzItY0CGmzP8UYZiYKl/2u+2TgFS5r7NWH11bxoUzjKdaa1NLw+ieA8GlBFfCbfWe6YVB9ggUte4VtYFMZGxOjS2bAiYtfgTKFJv+XqORAwExG6+G2eDxIDyo80/OA9IG7Xv/jwQr7D6KDjDuULFcN/iTxuttoKrHeYz1hf5ZQlBdllwJHYx6fK2g8kha6r2JIQKocvsAXiiONqSfw== hello@world.com" + } + } + network_profile { + name = "TestNetworkProfile" + primary = true + ip_configuration { + name = "TestIPConfiguration" + subnet_id = "${azurerm_subnet.test.id}" + load_balancer_backend_address_pool_ids = ["${azurerm_lb_backend_address_pool.test.id}"] + } + } + storage_profile_os_disk { + name = "osDiskProfile" + caching = "ReadWrite" + create_option = "FromImage" + os_type = "linux" + 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, rInt, rInt, rInt) +} + func testAccAzureRMVirtualMachineScaleSet_basicLinux_managedDisk(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/website/docs/r/virtual_machine_scale_set.html.markdown b/website/docs/r/virtual_machine_scale_set.html.markdown index f92704ccaefb..5505451466eb 100644 --- a/website/docs/r/virtual_machine_scale_set.html.markdown +++ b/website/docs/r/virtual_machine_scale_set.html.markdown @@ -273,7 +273,7 @@ The following arguments are supported: * `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 15 characters long. * `admin_username` - (Required) Specifies the administrator account name to use for all the instances of virtual machines in the scale set. * `admin_password` - (Required) Specifies the administrator password to use for all the instances of virtual machines in a scale set.. -* `custom_data` - (Optional) Specifies custom data to supply to the machine. On linux-based systems, this can be used as a cloud-init script. On other systems, this will be copied as a file on disk. Internally, Terraform will base64 encode this value before sending it to the API. The maximum length of the binary array is 65535 bytes. Changing this forces a new resource to be created. +* `custom_data` - (Optional) Specifies custom data to supply to the machine. On linux-based systems, this can be used as a cloud-init script. On other systems, this will be copied as a file on disk. Internally, Terraform will base64 encode this value before sending it to the API. The maximum length of the binary array is 65535 bytes. `os_profile_secrets` supports the following: