From 34206af08f89cb997c2a55ed31106dff6bcaa35d Mon Sep 17 00:00:00 2001 From: Zhenhua Hu Date: Wed, 21 Feb 2024 05:52:02 +0800 Subject: [PATCH] `azurerm_linux_virtual_machine`,`azurerm_windows_virtual_machine` - Support update `virtual_machine_scale_set_id` (#24768) * add code * add notes for enabling the preview feature --- .../compute/linux_virtual_machine_resource.go | 13 +- ...tual_machine_resource_orchestrated_test.go | 148 ++++++++++++++++++ .../windows_virtual_machine_resource.go | 13 +- ...tual_machine_resource_orchestrated_test.go | 146 +++++++++++++++++ .../r/linux_virtual_machine.html.markdown | 4 +- .../r/windows_virtual_machine.html.markdown | 4 +- 6 files changed, 324 insertions(+), 4 deletions(-) diff --git a/internal/services/compute/linux_virtual_machine_resource.go b/internal/services/compute/linux_virtual_machine_resource.go index 1493cb62dfb4..b455324b7b82 100644 --- a/internal/services/compute/linux_virtual_machine_resource.go +++ b/internal/services/compute/linux_virtual_machine_resource.go @@ -330,7 +330,6 @@ func resourceLinuxVirtualMachine() *pluginsdk.Resource { "virtual_machine_scale_set_id": { Type: pluginsdk.TypeString, Optional: true, - ForceNew: true, ConflictsWith: []string{ "availability_set_id", }, @@ -1194,6 +1193,18 @@ func resourceLinuxVirtualMachineUpdate(d *pluginsdk.ResourceData, meta interface } } + if d.HasChange("virtual_machine_scale_set_id") { + shouldUpdate = true + + if vmssIDRaw, ok := d.GetOk("virtual_machine_scale_set_id"); ok { + update.VirtualMachineProperties.VirtualMachineScaleSet = &compute.SubResource{ + ID: utils.String(vmssIDRaw.(string)), + } + } else { + update.VirtualMachineProperties.VirtualMachineScaleSet = &compute.SubResource{} + } + } + if d.HasChange("proximity_placement_group_id") { shouldUpdate = true diff --git a/internal/services/compute/linux_virtual_machine_resource_orchestrated_test.go b/internal/services/compute/linux_virtual_machine_resource_orchestrated_test.go index 9707ae79d25d..5e03afb3c91b 100644 --- a/internal/services/compute/linux_virtual_machine_resource_orchestrated_test.go +++ b/internal/services/compute/linux_virtual_machine_resource_orchestrated_test.go @@ -101,6 +101,35 @@ func TestAccLinuxVirtualMachine_orchestratedMultipleNonZonal(t *testing.T) { }) } +func TestAccLinuxVirtualMachine_orchestratedIdUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_virtual_machine", "test") + r := LinuxVirtualMachineResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.orchestratedIdUnAttached(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password"), + { + Config: r.orchestratedIdAttached(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password"), + { + Config: r.orchestratedIdUnAttached(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password"), + }) +} + func (r LinuxVirtualMachineResource) orchestratedZonal(data acceptance.TestData) string { return fmt.Sprintf(` %s @@ -161,6 +190,125 @@ resource "azurerm_linux_virtual_machine" "test" { `, r.templateBaseForOchestratedVMSS(data), data.RandomInteger, data.RandomInteger, data.RandomInteger) } +func (r LinuxVirtualMachineResource) orchestratedIdUnAttached(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_network_interface" "test" { + name = "acctestnic-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + ip_configuration { + name = "internal" + subnet_id = azurerm_subnet.test.id + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_orchestrated_virtual_machine_scale_set" "test" { + name = "acctestVMO-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + platform_fault_domain_count = 1 + + zones = ["1"] + + tags = { + ENV = "Test" + } +} + +resource "azurerm_linux_virtual_machine" "test" { + name = "acctestVM-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + size = "Standard_F2" + admin_username = "adminuser" + admin_password = "P@ssw0rd1234!" + disable_password_authentication = false + network_interface_ids = [ + azurerm_network_interface.test.id, + ] + + source_image_reference { + publisher = "Canonical" + offer = "0001-com-ubuntu-server-jammy" + sku = "22_04-lts" + version = "latest" + } + + os_disk { + storage_account_type = "Standard_LRS" + caching = "ReadWrite" + } + + zone = tolist(azurerm_orchestrated_virtual_machine_scale_set.test.zones)[0] +} +`, r.templateBaseForOchestratedVMSS(data), data.RandomInteger, data.RandomInteger, data.RandomInteger) +} + +func (r LinuxVirtualMachineResource) orchestratedIdAttached(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_network_interface" "test" { + name = "acctestnic-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + ip_configuration { + name = "internal" + subnet_id = azurerm_subnet.test.id + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_orchestrated_virtual_machine_scale_set" "test" { + name = "acctestVMO-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + platform_fault_domain_count = 1 + + zones = ["1"] + + tags = { + ENV = "Test" + } +} + +resource "azurerm_linux_virtual_machine" "test" { + name = "acctestVM-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + size = "Standard_F2" + admin_username = "adminuser" + admin_password = "P@ssw0rd1234!" + disable_password_authentication = false + network_interface_ids = [ + azurerm_network_interface.test.id, + ] + + source_image_reference { + publisher = "Canonical" + offer = "0001-com-ubuntu-server-jammy" + sku = "22_04-lts" + version = "latest" + } + + os_disk { + storage_account_type = "Standard_LRS" + caching = "ReadWrite" + } + + virtual_machine_scale_set_id = azurerm_orchestrated_virtual_machine_scale_set.test.id + zone = tolist(azurerm_orchestrated_virtual_machine_scale_set.test.zones)[0] +} +`, r.templateBaseForOchestratedVMSS(data), data.RandomInteger, data.RandomInteger, data.RandomInteger) +} + func (r LinuxVirtualMachineResource) orchestratedWithPlatformFaultDomain(data acceptance.TestData) string { return fmt.Sprintf(` %s diff --git a/internal/services/compute/windows_virtual_machine_resource.go b/internal/services/compute/windows_virtual_machine_resource.go index b05a7ccb2d10..4ca9b55b3cae 100644 --- a/internal/services/compute/windows_virtual_machine_resource.go +++ b/internal/services/compute/windows_virtual_machine_resource.go @@ -359,7 +359,6 @@ func resourceWindowsVirtualMachine() *pluginsdk.Resource { "virtual_machine_scale_set_id": { Type: pluginsdk.TypeString, Optional: true, - ForceNew: true, ConflictsWith: []string{ "availability_set_id", }, @@ -1355,6 +1354,18 @@ func resourceWindowsVirtualMachineUpdate(d *pluginsdk.ResourceData, meta interfa } } + if d.HasChange("virtual_machine_scale_set_id") { + shouldUpdate = true + + if vmssIDRaw, ok := d.GetOk("virtual_machine_scale_set_id"); ok { + update.VirtualMachineProperties.VirtualMachineScaleSet = &compute.SubResource{ + ID: utils.String(vmssIDRaw.(string)), + } + } else { + update.VirtualMachineProperties.VirtualMachineScaleSet = &compute.SubResource{} + } + } + if d.HasChange("proximity_placement_group_id") { shouldUpdate = true diff --git a/internal/services/compute/windows_virtual_machine_resource_orchestrated_test.go b/internal/services/compute/windows_virtual_machine_resource_orchestrated_test.go index 001a0029d8c8..5bebb1194860 100644 --- a/internal/services/compute/windows_virtual_machine_resource_orchestrated_test.go +++ b/internal/services/compute/windows_virtual_machine_resource_orchestrated_test.go @@ -101,6 +101,35 @@ func TestAccWindowsVirtualMachine_orchestratedMultipleNoneZonal(t *testing.T) { }) } +func TestAccWindowsVirtualMachine_orchestratedIdUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_virtual_machine", "test") + r := WindowsVirtualMachineResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.orchestratedIdUnAttached(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password"), + { + Config: r.orchestratedIdAttached(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password"), + { + Config: r.orchestratedIdUnAttached(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password"), + }) +} + func (r WindowsVirtualMachineResource) orchestratedZonal(data acceptance.TestData) string { return fmt.Sprintf(` %s @@ -160,6 +189,123 @@ resource "azurerm_windows_virtual_machine" "test" { `, r.templateBaseForOchestratedVMSS(data), data.RandomInteger, data.RandomInteger) } +func (r WindowsVirtualMachineResource) orchestratedIdUnAttached(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_network_interface" "test" { + name = "acctestnic-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + ip_configuration { + name = "internal" + subnet_id = azurerm_subnet.test.id + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_orchestrated_virtual_machine_scale_set" "test" { + name = "acctestVMO-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + platform_fault_domain_count = 1 + + zones = ["1"] + + tags = { + ENV = "Test" + } +} + +resource "azurerm_windows_virtual_machine" "test" { + name = local.vm_name + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + size = "Standard_F2" + admin_username = "adminuser" + admin_password = "P@ssw0rd1234!" + network_interface_ids = [ + azurerm_network_interface.test.id, + ] + + source_image_reference { + publisher = "MicrosoftWindowsServer" + offer = "WindowsServer" + sku = "2016-Datacenter" + version = "latest" + } + + os_disk { + storage_account_type = "Standard_LRS" + caching = "ReadWrite" + } + + zone = tolist(azurerm_orchestrated_virtual_machine_scale_set.test.zones)[0] +} +`, r.templateBaseForOchestratedVMSS(data), data.RandomInteger, data.RandomInteger) +} + +func (r WindowsVirtualMachineResource) orchestratedIdAttached(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_network_interface" "test" { + name = "acctestnic-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + ip_configuration { + name = "internal" + subnet_id = azurerm_subnet.test.id + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_orchestrated_virtual_machine_scale_set" "test" { + name = "acctestVMO-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + platform_fault_domain_count = 1 + + zones = ["1"] + + tags = { + ENV = "Test" + } +} + +resource "azurerm_windows_virtual_machine" "test" { + name = local.vm_name + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + size = "Standard_F2" + admin_username = "adminuser" + admin_password = "P@ssw0rd1234!" + network_interface_ids = [ + azurerm_network_interface.test.id, + ] + + source_image_reference { + publisher = "MicrosoftWindowsServer" + offer = "WindowsServer" + sku = "2016-Datacenter" + version = "latest" + } + + os_disk { + storage_account_type = "Standard_LRS" + caching = "ReadWrite" + } + + virtual_machine_scale_set_id = azurerm_orchestrated_virtual_machine_scale_set.test.id + zone = tolist(azurerm_orchestrated_virtual_machine_scale_set.test.zones)[0] +} +`, r.templateBaseForOchestratedVMSS(data), data.RandomInteger, data.RandomInteger) +} + func (r WindowsVirtualMachineResource) orchestratedWithPlatformFaultDomain(data acceptance.TestData) string { return fmt.Sprintf(` %s diff --git a/website/docs/r/linux_virtual_machine.html.markdown b/website/docs/r/linux_virtual_machine.html.markdown index c6e638189d71..8a99b4eb0798 100644 --- a/website/docs/r/linux_virtual_machine.html.markdown +++ b/website/docs/r/linux_virtual_machine.html.markdown @@ -210,7 +210,9 @@ The following arguments are supported: * `vtpm_enabled` - (Optional) Specifies whether vTPM should be enabled on the virtual machine. Changing this forces a new resource to be created. -* `virtual_machine_scale_set_id` - (Optional) Specifies the Orchestrated Virtual Machine Scale Set that this Virtual Machine should be created within. Changing this forces a new resource to be created. +* `virtual_machine_scale_set_id` - (Optional) Specifies the Orchestrated Virtual Machine Scale Set that this Virtual Machine should be created within. + +-> **NOTE:** To update `virtual_machine_scale_set_id` the Preview Feature `Microsoft.Compute/SingleFDAttachDetachVMToVmss` needs to be enabled, see [the documentation](https://review.learn.microsoft.com/azure/virtual-machine-scale-sets/virtual-machine-scale-sets-attach-detach-vm#enroll-in-the-preview) for more information. ~> **NOTE:** Orchestrated Virtual Machine Scale Sets can be provisioned using [the `azurerm_orchestrated_virtual_machine_scale_set` resource](/docs/providers/azurerm/r/orchestrated_virtual_machine_scale_set.html). diff --git a/website/docs/r/windows_virtual_machine.html.markdown b/website/docs/r/windows_virtual_machine.html.markdown index d7465ead241a..e859c5f0aa24 100644 --- a/website/docs/r/windows_virtual_machine.html.markdown +++ b/website/docs/r/windows_virtual_machine.html.markdown @@ -205,7 +205,9 @@ The following arguments are supported: * `user_data` - (Optional) The Base64-Encoded User Data which should be used for this Virtual Machine. -* `virtual_machine_scale_set_id` - (Optional) Specifies the Orchestrated Virtual Machine Scale Set that this Virtual Machine should be created within. Changing this forces a new resource to be created. +* `virtual_machine_scale_set_id` - (Optional) Specifies the Orchestrated Virtual Machine Scale Set that this Virtual Machine should be created within. + +-> **NOTE:** To update `virtual_machine_scale_set_id` the Preview Feature `Microsoft.Compute/SingleFDAttachDetachVMToVmss` needs to be enabled, see [the documentation](https://review.learn.microsoft.com/azure/virtual-machine-scale-sets/virtual-machine-scale-sets-attach-detach-vm#enroll-in-the-preview) for more information. ~> **NOTE:** Orchestrated Virtual Machine Scale Sets can be provisioned using [the `azurerm_orchestrated_virtual_machine_scale_set` resource](/docs/providers/azurerm/r/orchestrated_virtual_machine_scale_set.html).