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:
parent
9aa9c15786
commit
e0bdc5f8f7
|
@ -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{
|
||||
|
@ -391,6 +445,7 @@ func resourceArmVirtualMachineScaleSetCreate(d *schema.ResourceData, meta interf
|
|||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue