diff --git a/azurerm/internal/features/user_flags.go b/azurerm/internal/features/user_flags.go index cccbff633907..009a73fe2dc5 100644 --- a/azurerm/internal/features/user_flags.go +++ b/azurerm/internal/features/user_flags.go @@ -10,6 +10,7 @@ type UserFeatures struct { type VirtualMachineFeatures struct { DeleteOSDiskOnDeletion bool + GracefulShutdown bool } type VirtualMachineScaleSetFeatures struct { diff --git a/azurerm/internal/provider/features.go b/azurerm/internal/provider/features.go index 7987aae295c3..04d7a4ee1f1e 100644 --- a/azurerm/internal/provider/features.go +++ b/azurerm/internal/provider/features.go @@ -19,7 +19,6 @@ func schemaFeatures(supportLegacyTestSuite bool) *schema.Schema { Type: schema.TypeBool, Optional: true, }, - "purge_soft_delete_on_destroy": { Type: schema.TypeBool, Optional: true, @@ -64,7 +63,11 @@ func schemaFeatures(supportLegacyTestSuite bool) *schema.Schema { Schema: map[string]*schema.Schema{ "delete_os_disk_on_deletion": { Type: schema.TypeBool, - Required: true, + Optional: true, + }, + "graceful_shutdown": { + Type: schema.TypeBool, + Optional: true, }, }, }, @@ -124,6 +127,7 @@ func expandFeatures(input []interface{}) features.UserFeatures { }, VirtualMachine: features.VirtualMachineFeatures{ DeleteOSDiskOnDeletion: true, + GracefulShutdown: false, }, VirtualMachineScaleSet: features.VirtualMachineScaleSetFeatures{ RollInstancesWhenRequired: true, @@ -176,6 +180,9 @@ func expandFeatures(input []interface{}) features.UserFeatures { if v, ok := virtualMachinesRaw["delete_os_disk_on_deletion"]; ok { features.VirtualMachine.DeleteOSDiskOnDeletion = v.(bool) } + if v, ok := virtualMachinesRaw["graceful_shutdown"]; ok { + features.VirtualMachine.GracefulShutdown = v.(bool) + } } } diff --git a/azurerm/internal/provider/features_test.go b/azurerm/internal/provider/features_test.go index b5b8d320eb69..0cd7ee892d82 100644 --- a/azurerm/internal/provider/features_test.go +++ b/azurerm/internal/provider/features_test.go @@ -59,6 +59,7 @@ func TestExpandFeatures(t *testing.T) { "virtual_machine": []interface{}{ map[string]interface{}{ "delete_os_disk_on_deletion": true, + "graceful_shutdown": true, }, }, "virtual_machine_scale_set": []interface{}{ @@ -81,6 +82,7 @@ func TestExpandFeatures(t *testing.T) { }, VirtualMachine: features.VirtualMachineFeatures{ DeleteOSDiskOnDeletion: true, + GracefulShutdown: true, }, VirtualMachineScaleSet: features.VirtualMachineScaleSetFeatures{ RollInstancesWhenRequired: true, @@ -94,6 +96,7 @@ func TestExpandFeatures(t *testing.T) { "virtual_machine": []interface{}{ map[string]interface{}{ "delete_os_disk_on_deletion": false, + "graceful_shutdown": false, }, }, "network_locking": []interface{}{ @@ -132,6 +135,7 @@ func TestExpandFeatures(t *testing.T) { }, VirtualMachine: features.VirtualMachineFeatures{ DeleteOSDiskOnDeletion: false, + GracefulShutdown: false, }, VirtualMachineScaleSet: features.VirtualMachineScaleSetFeatures{ RollInstancesWhenRequired: false, @@ -366,16 +370,18 @@ func TestExpandFeaturesVirtualMachine(t *testing.T) { Expected: features.UserFeatures{ VirtualMachine: features.VirtualMachineFeatures{ DeleteOSDiskOnDeletion: true, + GracefulShutdown: false, }, }, }, { - Name: "Delete OS Disk Enabled", + Name: "Delete OS Disk and Graceful Shutdown Enabled", Input: []interface{}{ map[string]interface{}{ "virtual_machine": []interface{}{ map[string]interface{}{ "delete_os_disk_on_deletion": true, + "graceful_shutdown": true, }, }, }, @@ -383,16 +389,18 @@ func TestExpandFeaturesVirtualMachine(t *testing.T) { Expected: features.UserFeatures{ VirtualMachine: features.VirtualMachineFeatures{ DeleteOSDiskOnDeletion: true, + GracefulShutdown: true, }, }, }, { - Name: "Delete OS Disk Disabled", + Name: "Delete OS Disk and Graceful Shutdown Disabled", Input: []interface{}{ map[string]interface{}{ "virtual_machine": []interface{}{ map[string]interface{}{ "delete_os_disk_on_deletion": false, + "graceful_shutdown": false, }, }, }, @@ -400,6 +408,7 @@ func TestExpandFeaturesVirtualMachine(t *testing.T) { Expected: features.UserFeatures{ VirtualMachine: features.VirtualMachineFeatures{ DeleteOSDiskOnDeletion: false, + GracefulShutdown: false, }, }, }, diff --git a/azurerm/internal/services/compute/linux_virtual_machine_resource.go b/azurerm/internal/services/compute/linux_virtual_machine_resource.go index aa8b74e55e2a..13ab1936a932 100644 --- a/azurerm/internal/services/compute/linux_virtual_machine_resource.go +++ b/azurerm/internal/services/compute/linux_virtual_machine_resource.go @@ -1088,10 +1088,8 @@ func resourceLinuxVirtualMachineDelete(d *schema.ResourceData, meta interface{}) // ISSUE: XXX // shutting down the Virtual Machine prior to removing it means users are no longer charged for the compute // thus this can be a large cost-saving when deleting larger instances - // in addition - since we're shutting down the machine to remove it, forcing a power-off is fine (as opposed - // to waiting for a graceful shut down) log.Printf("[DEBUG] Powering Off Linux Virtual Machine %q (Resource Group %q)..", id.Name, id.ResourceGroup) - skipShutdown := true + skipShutdown := !meta.(*clients.Client).Features.VirtualMachine.GracefulShutdown powerOffFuture, err := client.PowerOff(ctx, id.ResourceGroup, id.Name, utils.Bool(skipShutdown)) if err != nil { return fmt.Errorf("powering off Linux Virtual Machine %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) diff --git a/azurerm/internal/services/compute/tests/linux_virtual_machine_resource_other_test.go b/azurerm/internal/services/compute/tests/linux_virtual_machine_resource_other_test.go index 535d4f305fa2..ab38f9e832db 100644 --- a/azurerm/internal/services/compute/tests/linux_virtual_machine_resource_other_test.go +++ b/azurerm/internal/services/compute/tests/linux_virtual_machine_resource_other_test.go @@ -613,6 +613,42 @@ func TestAccLinuxVirtualMachine_otherEncryptionAtHostEnabledWithCMK(t *testing.T }) } +func TestAccLinuxVirtualMachine_otherGracefulShutdownDisabled(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_virtual_machine", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: checkLinuxVirtualMachineIsDestroyed, + Steps: []resource.TestStep{ + { + Config: testLinuxVirtualMachine_otherGracefulShutdown(data, false), + Check: resource.ComposeTestCheckFunc( + checkLinuxVirtualMachineExists(data.ResourceName), + ), + }, + }, + }) +} + +func TestAccLinuxVirtualMachine_otherGracefulShutdownEnabled(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_virtual_machine", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: checkLinuxVirtualMachineIsDestroyed, + Steps: []resource.TestStep{ + { + Config: testLinuxVirtualMachine_otherGracefulShutdown(data, true), + Check: resource.ComposeTestCheckFunc( + checkLinuxVirtualMachineExists(data.ResourceName), + ), + }, + }, + }) +} + func testLinuxVirtualMachine_otherAllowExtensionOperationsDefault(data acceptance.TestData) string { template := testLinuxVirtualMachine_template(data) return fmt.Sprintf(` @@ -1739,3 +1775,78 @@ resource "azurerm_linux_virtual_machine" "test" { } `, template, data.RandomInteger, enabled) } + +func testLinuxVirtualMachine_otherGracefulShutdown(data acceptance.TestData, gracefulShutdown bool) string { + return fmt.Sprintf(` +locals { + first_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+wWK73dCr+jgQOAxNsHAnNNNMEMWOHYEccp6wJm2gotpr9katuF/ZAdou5AaW1C61slRkHRkpRRX9FA9CYBiitZgvCCz+3nWNN7l/Up54Zps/pHWGZLHNJZRYyAB6j5yVLMVHIHriY49d/GZTZVNB8GoJv9Gakwc/fuEZYYl4YDFiGMBP///TzlI4jhiJzjKnEvqPFki5p2ZRJqcbCiF4pJrxUQR/RXqVFQdbRLZgYfJ8xGB878RENq3yQ39d8dVOkq4edbkzwcUmwwwkYVPIoDGsYLaRHnG+To7FvMeyO7xDVQkMKzopTQV8AuKpyvpqu0a9pWOMaiCyDytO7GGN you@me.com" +} + +provider "azurerm" { + features { + virtual_machine { + graceful_shutdown = %t + } + } +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestnw-%d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "internal" + 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_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_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" + network_interface_ids = [ + azurerm_network_interface.test.id, + ] + + admin_ssh_key { + username = "adminuser" + public_key = local.first_public_key + } + + os_disk { + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + } + + source_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } +} +`, gracefulShutdown, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) +} diff --git a/azurerm/internal/services/compute/tests/windows_virtual_machine_resource_other_test.go b/azurerm/internal/services/compute/tests/windows_virtual_machine_resource_other_test.go index 1a9e5e415bac..601145e5f134 100644 --- a/azurerm/internal/services/compute/tests/windows_virtual_machine_resource_other_test.go +++ b/azurerm/internal/services/compute/tests/windows_virtual_machine_resource_other_test.go @@ -1124,6 +1124,48 @@ resource "azurerm_windows_virtual_machine" "test" { `, template) } +func TestAccWindowsVirtualMachine_otherGracefulShutdownDisabled(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_virtual_machine", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: checkWindowsVirtualMachineIsDestroyed, + Steps: []resource.TestStep{ + { + Config: testWindowsVirtualMachine_otherGracefulShutdown(data, false), + Check: resource.ComposeTestCheckFunc( + checkWindowsVirtualMachineExists(data.ResourceName), + ), + }, + data.ImportStep( + "admin_password", + ), + }, + }) +} + +func TestAccWindowsVirtualMachine_otherGracefulShutdownEnabled(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_virtual_machine", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: checkWindowsVirtualMachineIsDestroyed, + Steps: []resource.TestStep{ + { + Config: testWindowsVirtualMachine_otherGracefulShutdown(data, true), + Check: resource.ComposeTestCheckFunc( + checkWindowsVirtualMachineExists(data.ResourceName), + ), + }, + data.ImportStep( + "admin_password", + ), + }, + }) +} + func testWindowsVirtualMachine_otherAdditionalUnattendContent(data acceptance.TestData) string { template := testWindowsVirtualMachine_template(data) return fmt.Sprintf(` @@ -2567,3 +2609,74 @@ resource "azurerm_windows_virtual_machine" "test" { } `, template, enabled) } + +func testWindowsVirtualMachine_otherGracefulShutdown(data acceptance.TestData, gracefulShutdown bool) string { + return fmt.Sprintf(` +locals { + vm_name = "acctestvm%s" +} + +provider "azurerm" { + features { + virtual_machine { + graceful_shutdown = %t + } + } +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestnw-%d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "internal" + 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_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_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@$$w0rd1234!" + network_interface_ids = [ + azurerm_network_interface.test.id, + ] + + os_disk { + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + } + + source_image_reference { + publisher = "MicrosoftWindowsServer" + offer = "WindowsServer" + sku = "2016-Datacenter" + version = "latest" + } +} +`, data.RandomString, gracefulShutdown, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) +} diff --git a/azurerm/internal/services/compute/windows_virtual_machine_resource.go b/azurerm/internal/services/compute/windows_virtual_machine_resource.go index 34fda7288386..fdb925683c4f 100644 --- a/azurerm/internal/services/compute/windows_virtual_machine_resource.go +++ b/azurerm/internal/services/compute/windows_virtual_machine_resource.go @@ -1170,10 +1170,8 @@ func resourceWindowsVirtualMachineDelete(d *schema.ResourceData, meta interface{ // ISSUE: XXX // shutting down the Virtual Machine prior to removing it means users are no longer charged for the compute // thus this can be a large cost-saving when deleting larger instances - // in addition - since we're shutting down the machine to remove it, forcing a power-off is fine (as opposed - // to waiting for a graceful shut down) log.Printf("[DEBUG] Powering Off Windows Virtual Machine %q (Resource Group %q)..", id.Name, id.ResourceGroup) - skipShutdown := true + skipShutdown := !meta.(*clients.Client).Features.VirtualMachine.GracefulShutdown powerOffFuture, err := client.PowerOff(ctx, id.ResourceGroup, id.Name, utils.Bool(skipShutdown)) if err != nil { return fmt.Errorf("powering off Windows Virtual Machine %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 4278caf86267..92c3d75c2237 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -169,9 +169,9 @@ The `features` block supports the following: The `key_vault` block supports the following: -* `recover_soft_deleted_key_vaults` - (Optional) Should the `azurerm_key_vault` resource recover a Key Vault which has previously been Soft Deleted? Defaults to `true`. +* `recover_soft_deleted_key_vaults` - (Optional) Should the `azurerm_key_vault` resource recover a Key Vault which has previously been Soft Deleted? Defaults to `true`. -* `purge_soft_delete_on_destroy` - (Optional) Should the `azurerm_key_vault` resource be permanently deleted (e.g. purged) when destroyed? Defaults to `true`. +* `purge_soft_delete_on_destroy` - (Optional) Should the `azurerm_key_vault` resource be permanently deleted (e.g. purged) when destroyed? Defaults to `true`. ~> **Note:** When purge protection is enabled, a key vault or an object in the deleted state cannot be purged until the retention period(90 days) has passed. @@ -191,6 +191,10 @@ The `virtual_machine` block supports the following: ~> **Note:** This does not affect the older `azurerm_virtual_machine` resource, which has its own flags for managing this within the resource. +* `graceful_shutdown` - (Optional) Should the `azurerm_linux_virtual_machine` and `azurerm_windows_virtual_machine` request a graceful shutdown when the Virtual Machine is destroyed? Defaults to `false`. + +~> **Note:** When using a graceful shutdown, Azure gives the Virtual Machine a 5 minutes window in which to complete the shutdown process, at which point the machine will be force powered off - [more information can be found in this blog post](https://azure.microsoft.com/en-us/blog/linux-and-graceful-shutdowns-2/). + --- The `virtual_machine_scale_set` block supports the following: