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..593e08ff8427 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,14 @@ 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 { + 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) return nil @@ -709,6 +772,39 @@ func flattenAzureRmVirtualMachineScaleSetSku(sku *compute.Sku) []interface{} { return []interface{}{result} } +func flattenAzureRmVirtualMachineScaleSetExtensionProfile(profile *compute.VirtualMachineScaleSetExtensionProfile) ([]map[string]interface{}, error) { + if profile.Extensions == nil { + return nil, 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 { + return nil, err + } + e["settings"] = settings + } + } + + result = append(result, e) + } + + return result, nil +} + func resourceArmVirtualMachineScaleSetStorageProfileImageReferenceHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) @@ -783,6 +879,20 @@ 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))) + 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()) +} + func expandVirtualMachineScaleSetSku(d *schema.ResourceData) (*compute.Sku, error) { skuConfig := d.Get("sku").(*schema.Set).List() @@ -1111,3 +1221,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..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 @@ -86,6 +86,44 @@ 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 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 @@ -240,6 +278,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 +578,207 @@ 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 = <