provider/azurerm: Add support for extensions on virtual machine scale sets (#12124)

* Add support for vmss extensions.

* Update website.

* Add multi extension test.

* Return error from settings parsing.

* Update extension hash.
This commit is contained in:
Robert Rudduck 2017-03-12 09:13:38 -05:00 committed by Paul Stack
parent 9aa9c15786
commit e0bdc5f8f7
3 changed files with 447 additions and 3 deletions

View File

@ -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
@ -702,6 +765,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{})
@ -776,6 +872,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()
@ -1091,3 +1201,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
}

View File

@ -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 = <<SETTINGS
{
"commandToExecute": "echo $HOSTNAME"
}
SETTINGS
protected_settings = <<SETTINGS
{
"storageAccountName": "${azurerm_storage_account.test.name}",
"storageAccountKey": "${azurerm_storage_account.test.primary_access_key}"
}
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 = <<SETTINGS
{
"commandToExecute": "echo $HOSTNAME"
}
SETTINGS
protected_settings = <<SETTINGS
{
"storageAccountName": "${azurerm_storage_account.test.name}",
"storageAccountKey": "${azurerm_storage_account.test.primary_access_key}"
}
SETTINGS
}
extension {
name = "Docker"
publisher = "Microsoft.Azure.Extensions"
type = "DockerExtension"
type_handler_version = "1.0"
auto_upgrade_minor_version = true
}
}
`

View File

@ -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: