diff --git a/helpers/azure/extended_location.go b/helpers/azure/extended_location.go new file mode 100644 index 000000000000..a386b9794091 --- /dev/null +++ b/helpers/azure/extended_location.go @@ -0,0 +1,15 @@ +package azure + +import ( + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +func SchemaExtendedLocation() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + } +} diff --git a/internal/services/compute/linux_virtual_machine_resource.go b/internal/services/compute/linux_virtual_machine_resource.go index 1d4c1d333ebe..f39440bc7ae7 100644 --- a/internal/services/compute/linux_virtual_machine_resource.go +++ b/internal/services/compute/linux_virtual_machine_resource.go @@ -184,6 +184,8 @@ func resourceLinuxVirtualMachine() *pluginsdk.Resource { }, false), }, + "extended_location": azure.SchemaExtendedLocation(), + "extensions_time_budget": { Type: pluginsdk.TypeString, Optional: true, @@ -468,6 +470,13 @@ func resourceLinuxVirtualMachineCreate(d *pluginsdk.ResourceData, meta interface Tags: tags.Expand(t), } + if v, ok := d.GetOk("extended_location"); ok { + params.ExtendedLocation = &compute.ExtendedLocation{ + Name: utils.String(v.(string)), + Type: compute.ExtendedLocationTypesEdgeZone, + } + } + if v, ok := d.GetOk("patch_mode"); ok { if v == string(compute.LinuxVMGuestPatchModeAutomaticByPlatform) && !provisionVMAgent { return fmt.Errorf("%q cannot be set to %q when %q is set to %q", "patch_mode", "AutomaticByPlatform", "provision_vm_agent", "false") @@ -638,6 +647,10 @@ func resourceLinuxVirtualMachineRead(d *pluginsdk.ResourceData, meta interface{} d.Set("location", azure.NormalizeLocation(*location)) } + if resp.ExtendedLocation != nil && resp.ExtendedLocation.Name != nil { + d.Set("extended_location", resp.ExtendedLocation.Name) + } + identity, err := flattenVirtualMachineIdentity(resp.Identity) if err != nil { return fmt.Errorf("flattening `identity`: %+v", err) diff --git a/internal/services/compute/linux_virtual_machine_resource_other_test.go b/internal/services/compute/linux_virtual_machine_resource_other_test.go index fa03c304adc7..b62df4475123 100644 --- a/internal/services/compute/linux_virtual_machine_resource_other_test.go +++ b/internal/services/compute/linux_virtual_machine_resource_other_test.go @@ -625,6 +625,28 @@ func TestAccLinuxVirtualMachine_otherVTpmEnabled(t *testing.T) { }) } +func TestAccLinuxVirtualMachine_otherExtendedLocation(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_virtual_machine", "test") + r := LinuxVirtualMachineResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.otherExtendedLocation(data, "Test1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.otherExtendedLocation(data, "Test2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccLinuxVirtualMachine_otherPatchModeAutomaticByPlatform(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_linux_virtual_machine", "test") r := LinuxVirtualMachineResource{} @@ -2038,3 +2060,84 @@ resource "azurerm_linux_virtual_machine" "test" { } `, r.template(data), data.RandomInteger) } + +func (r LinuxVirtualMachineResource) otherExtendedLocation(data acceptance.TestData, tag string) string { + return fmt.Sprintf(` +# note: whilst these aren't used in all tests, it saves us redefining these everywhere +locals { + first_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+wWK73dCr+jgQOAxNsHAnNNNMEMWOHYEccp6wJm2gotpr9katuF/ZAdou5AaW1C61slRkHRkpRRX9FA9CYBiitZgvCCz+3nWNN7l/Up54Zps/pHWGZLHNJZRYyAB6j5yVLMVHIHriY49d/GZTZVNB8GoJv9Gakwc/fuEZYYl4YDFiGMBP///TzlI4jhiJzjKnEvqPFki5p2ZRJqcbCiF4pJrxUQR/RXqVFQdbRLZgYfJ8xGB878RENq3yQ39d8dVOkq4edbkzwcUmwwwkYVPIoDGsYLaRHnG+To7FvMeyO7xDVQkMKzopTQV8AuKpyvpqu0a9pWOMaiCyDytO7GGN you@me.com" + second_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0/NDMj2wG6bSa6jbn6E3LYlUsYiWMp1CQ2sGAijPALW6OrSu30lz7nKpoh8Qdw7/A4nAJgweI5Oiiw5/BOaGENM70Go+VM8LQMSxJ4S7/8MIJEZQp5HcJZ7XDTcEwruknrd8mllEfGyFzPvJOx6QAQocFhXBW6+AlhM3gn/dvV5vdrO8ihjET2GoDUqXPYC57ZuY+/Fz6W3KV8V97BvNUhpY5yQrP5VpnyvvXNFQtzDfClTvZFPuoHQi3/KYPi6O0FSD74vo8JOBZZY09boInPejkm9fvHQqfh0bnN7B6XJoUwC1Qprrx+XIy7ust5AEn5XL7d4lOvcR14MxDDKEp you@me.com" +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + + // There is no supported extended location in "West Europe" + location = "westus" +} + +data "azurerm_extended_locations" "test" { + location = azurerm_resource_group.test.location +} + +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_D2s_v3" + admin_username = "adminuser" + extended_location = data.azurerm_extended_locations.test.extended_locations.0 + + 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 = "Premium_LRS" + } + + source_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "18.04-LTS" + version = "latest" + } + + tags = { + ENV = "%s" + } +} +`, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, tag) +} diff --git a/internal/services/compute/linux_virtual_machine_scale_set_other_resource_test.go b/internal/services/compute/linux_virtual_machine_scale_set_other_resource_test.go index b6afd8eb3da1..baaca3a343b6 100644 --- a/internal/services/compute/linux_virtual_machine_scale_set_other_resource_test.go +++ b/internal/services/compute/linux_virtual_machine_scale_set_other_resource_test.go @@ -632,6 +632,28 @@ func TestAccLinuxVirtualMachineScaleSet_otherVTpmEnabled(t *testing.T) { }) } +func TestAccLinuxVirtualMachineScaleSet_otherExtendedLocation(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_virtual_machine_scale_set", "test") + r := LinuxVirtualMachineScaleSetResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.otherExtendedLocation(data, "Test1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password"), + { + Config: r.otherExtendedLocation(data, "Test2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password"), + }) +} + func (r LinuxVirtualMachineScaleSetResource) otherBootDiagnostics(data acceptance.TestData) string { return fmt.Sprintf(` %s @@ -2589,3 +2611,78 @@ resource "azurerm_linux_virtual_machine_scale_set" "test" { } `, r.template(data), data.RandomInteger) } + +func (r LinuxVirtualMachineScaleSetResource) otherExtendedLocation(data acceptance.TestData, tag string) string { + return fmt.Sprintf(` +# note: whilst these aren't used in all tests, it saves us redefining these everywhere +locals { + first_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+wWK73dCr+jgQOAxNsHAnNNNMEMWOHYEccp6wJm2gotpr9katuF/ZAdou5AaW1C61slRkHRkpRRX9FA9CYBiitZgvCCz+3nWNN7l/Up54Zps/pHWGZLHNJZRYyAB6j5yVLMVHIHriY49d/GZTZVNB8GoJv9Gakwc/fuEZYYl4YDFiGMBP///TzlI4jhiJzjKnEvqPFki5p2ZRJqcbCiF4pJrxUQR/RXqVFQdbRLZgYfJ8xGB878RENq3yQ39d8dVOkq4edbkzwcUmwwwkYVPIoDGsYLaRHnG+To7FvMeyO7xDVQkMKzopTQV8AuKpyvpqu0a9pWOMaiCyDytO7GGN you@me.com" + second_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0/NDMj2wG6bSa6jbn6E3LYlUsYiWMp1CQ2sGAijPALW6OrSu30lz7nKpoh8Qdw7/A4nAJgweI5Oiiw5/BOaGENM70Go+VM8LQMSxJ4S7/8MIJEZQp5HcJZ7XDTcEwruknrd8mllEfGyFzPvJOx6QAQocFhXBW6+AlhM3gn/dvV5vdrO8ihjET2GoDUqXPYC57ZuY+/Fz6W3KV8V97BvNUhpY5yQrP5VpnyvvXNFQtzDfClTvZFPuoHQi3/KYPi6O0FSD74vo8JOBZZY09boInPejkm9fvHQqfh0bnN7B6XJoUwC1Qprrx+XIy7ust5AEn5XL7d4lOvcR14MxDDKEp you@me.com" +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + + // There is no supported extended location in "West Europe" + location = "westus" +} + +data "azurerm_extended_locations" "test" { + location = azurerm_resource_group.test.location +} + +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_prefixes = ["10.0.2.0/24"] +} + +resource "azurerm_linux_virtual_machine_scale_set" "test" { + name = "acctestvmss-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "Standard_D2s_v3" + instances = 1 + admin_username = "adminuser" + admin_password = "P@ssword1234!" + extended_location = data.azurerm_extended_locations.test.extended_locations.0 + + disable_password_authentication = false + + source_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "18.04-LTS" + version = "latest" + } + + os_disk { + storage_account_type = "Premium_LRS" + caching = "ReadWrite" + } + + network_interface { + name = "example" + primary = true + + ip_configuration { + name = "internal" + primary = true + subnet_id = azurerm_subnet.test.id + } + } + + tags = { + ENV = "%s" + } +} +`, data.RandomInteger, data.RandomInteger, data.RandomInteger, tag) +} diff --git a/internal/services/compute/linux_virtual_machine_scale_set_resource.go b/internal/services/compute/linux_virtual_machine_scale_set_resource.go index 44ad44752132..0d83a0edcbea 100644 --- a/internal/services/compute/linux_virtual_machine_scale_set_resource.go +++ b/internal/services/compute/linux_virtual_machine_scale_set_resource.go @@ -146,6 +146,8 @@ func resourceLinuxVirtualMachineScaleSet() *pluginsdk.Resource { }, false), }, + "extended_location": azure.SchemaExtendedLocation(), + "extension": VirtualMachineScaleSetExtensionsSchema(), "extensions_time_budget": { @@ -573,6 +575,13 @@ func resourceLinuxVirtualMachineScaleSetCreate(d *pluginsdk.ResourceData, meta i props.Zones = azure.ExpandZones(zonesRaw) } + if v, ok := d.GetOk("extended_location"); ok { + props.ExtendedLocation = &compute.ExtendedLocation{ + Name: utils.String(v.(string)), + Type: compute.ExtendedLocationTypesEdgeZone, + } + } + if v, ok := d.GetOk("platform_fault_domain_count"); ok { props.VirtualMachineScaleSetProperties.PlatformFaultDomainCount = utils.Int32(int32(v.(int))) } @@ -946,6 +955,10 @@ func resourceLinuxVirtualMachineScaleSetRead(d *pluginsdk.ResourceData, meta int d.Set("location", location.NormalizeNilable(resp.Location)) d.Set("zones", zones.Flatten(resp.Zones)) + if resp.ExtendedLocation != nil && resp.ExtendedLocation.Name != nil { + d.Set("extended_location", resp.ExtendedLocation.Name) + } + var skuName *string var instances int if resp.Sku != nil { diff --git a/internal/services/compute/managed_disk_resource.go b/internal/services/compute/managed_disk_resource.go index 9a52ac862ee6..d836cbfadec2 100644 --- a/internal/services/compute/managed_disk_resource.go +++ b/internal/services/compute/managed_disk_resource.go @@ -186,6 +186,8 @@ func resourceManagedDisk() *pluginsdk.Resource { "encryption_settings": encryptionSettingsSchema(), + "extended_location": azure.SchemaExtendedLocation(), + "network_access_policy": { Type: pluginsdk.TypeString, Optional: true, @@ -483,6 +485,13 @@ func resourceManagedDiskCreate(d *pluginsdk.ResourceData, meta interface{}) erro createDisk.Zones = azure.ExpandZones(d.Get("zones").([]interface{})) } + if v, ok := d.GetOk("extended_location"); ok { + createDisk.ExtendedLocation = &compute.ExtendedLocation{ + Name: utils.String(v.(string)), + Type: compute.ExtendedLocationTypesEdgeZone, + } + } + future, err := client.CreateOrUpdate(ctx, resourceGroup, name, createDisk) if err != nil { return fmt.Errorf("creating/updating Managed Disk %q (Resource Group %q): %+v", name, resourceGroup, err) @@ -836,6 +845,10 @@ func resourceManagedDiskRead(d *pluginsdk.ResourceData, meta interface{}) error d.Set("zones", utils.FlattenStringSlice(resp.Zones)) } + if resp.ExtendedLocation != nil && resp.ExtendedLocation.Name != nil { + d.Set("extended_location", resp.ExtendedLocation.Name) + } + if sku := resp.Sku; sku != nil { d.Set("storage_account_type", string(sku.Name)) } diff --git a/internal/services/compute/managed_disk_resource_test.go b/internal/services/compute/managed_disk_resource_test.go index c088881a9bac..9d114126937b 100644 --- a/internal/services/compute/managed_disk_resource_test.go +++ b/internal/services/compute/managed_disk_resource_test.go @@ -618,6 +618,28 @@ func TestAccManagedDisk_create_withHyperVGeneration(t *testing.T) { }) } +func TestAccManagedDisk_extendedLocation(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_managed_disk", "test") + r := ManagedDiskResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.extendedLocation(data, "Test1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.extendedLocation(data, "Test2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (ManagedDiskResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.ManagedDiskID(state.ID) if err != nil { @@ -2272,3 +2294,36 @@ resource "azurerm_managed_disk" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } + +func (ManagedDiskResource) extendedLocation(data acceptance.TestData, tag string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + + // There is no supported extended location in "West Europe" + location = "westus" +} + +data "azurerm_extended_locations" "test" { + location = azurerm_resource_group.test.location +} + +resource "azurerm_managed_disk" "test" { + name = "acctestd-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + storage_account_type = "Premium_LRS" + create_option = "Empty" + disk_size_gb = "1" + extended_location = data.azurerm_extended_locations.test.extended_locations.0 + + tags = { + ENV = "%s" + } +} +`, data.RandomInteger, data.RandomInteger, tag) +} diff --git a/internal/services/compute/windows_virtual_machine_resource.go b/internal/services/compute/windows_virtual_machine_resource.go index b082401cc330..7dcaba493c80 100644 --- a/internal/services/compute/windows_virtual_machine_resource.go +++ b/internal/services/compute/windows_virtual_machine_resource.go @@ -185,6 +185,8 @@ func resourceWindowsVirtualMachine() *pluginsdk.Resource { }, false), }, + "extended_location": azure.SchemaExtendedLocation(), + "extensions_time_budget": { Type: pluginsdk.TypeString, Optional: true, @@ -499,6 +501,13 @@ func resourceWindowsVirtualMachineCreate(d *pluginsdk.ResourceData, meta interfa Tags: tags.Expand(t), } + if v, ok := d.GetOk("extended_location"); ok { + params.ExtendedLocation = &compute.ExtendedLocation{ + Name: utils.String(v.(string)), + Type: compute.ExtendedLocationTypesEdgeZone, + } + } + if !provisionVMAgent && allowExtensionOperations { return fmt.Errorf("`allow_extension_operations` cannot be set to `true` when `provision_vm_agent` is set to `false`") } @@ -704,6 +713,10 @@ func resourceWindowsVirtualMachineRead(d *pluginsdk.ResourceData, meta interface d.Set("location", azure.NormalizeLocation(*location)) } + if resp.ExtendedLocation != nil && resp.ExtendedLocation.Name != nil { + d.Set("extended_location", resp.ExtendedLocation.Name) + } + identity, err := flattenVirtualMachineIdentity(resp.Identity) if err != nil { return fmt.Errorf("flattening `identity`: %+v", err) diff --git a/internal/services/compute/windows_virtual_machine_resource_other_test.go b/internal/services/compute/windows_virtual_machine_resource_other_test.go index 0d896866f2c2..8ae64dbce84e 100644 --- a/internal/services/compute/windows_virtual_machine_resource_other_test.go +++ b/internal/services/compute/windows_virtual_machine_resource_other_test.go @@ -1093,6 +1093,28 @@ resource "azurerm_windows_virtual_machine" "test" { `, r.template(data)) } +func TestAccWindowsVirtualMachine_otherExtendedLocation(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_virtual_machine", "test") + r := WindowsVirtualMachineResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.otherExtendedLocation(data, "Test1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password"), + { + Config: r.otherExtendedLocation(data, "Test2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password"), + }) +} + func (r WindowsVirtualMachineResource) otherAdditionalUnattendContent(data acceptance.TestData) string { return fmt.Sprintf(` %s @@ -2643,3 +2665,80 @@ resource "azurerm_windows_virtual_machine" "test" { } `, data.RandomString, gracefulShutdown, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) } + +func (r WindowsVirtualMachineResource) otherExtendedLocation(data acceptance.TestData, tag string) string { + return fmt.Sprintf(` +locals { + vm_name = "acctestvm%s" +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + // There is no supported extended location in "West Europe" + location = "westus" +} + +data "azurerm_extended_locations" "test" { + location = azurerm_resource_group.test.location +} + +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_D2s_v3" + admin_username = "adminuser" + admin_password = "P@$$w0rd1234!" + extended_location = data.azurerm_extended_locations.test.extended_locations.0 + + network_interface_ids = [ + azurerm_network_interface.test.id, + ] + + os_disk { + caching = "ReadWrite" + storage_account_type = "Premium_LRS" + } + + source_image_reference { + publisher = "MicrosoftWindowsServer" + offer = "WindowsServer" + sku = "2016-Datacenter" + version = "latest" + } + + enable_automatic_updates = false + patch_mode = "Manual" + + tags = { + ENV = "%s" + } +} +`, data.RandomString, data.RandomInteger, data.RandomInteger, data.RandomInteger, tag) +} diff --git a/internal/services/compute/windows_virtual_machine_scale_set_other_resource_test.go b/internal/services/compute/windows_virtual_machine_scale_set_other_resource_test.go index 92ec34252b36..199fc60f6837 100644 --- a/internal/services/compute/windows_virtual_machine_scale_set_other_resource_test.go +++ b/internal/services/compute/windows_virtual_machine_scale_set_other_resource_test.go @@ -770,6 +770,28 @@ func TestAccWindowsVirtualMachineScaleSet_otherLicenseTypeUpdated(t *testing.T) }) } +func TestAccWindowsVirtualMachineScaleSet_otherExtendedLocation(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_virtual_machine_scale_set", "test") + r := WindowsVirtualMachineScaleSetResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.otherExtendedLocation(data, "Test1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password"), + { + Config: r.otherExtendedLocation(data, "Test2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password"), + }) +} + func (WindowsVirtualMachineScaleSetResource) otherAdditionalUnattendContent(data acceptance.TestData) string { template := WindowsVirtualMachineScaleSetResource{}.template(data) return fmt.Sprintf(` @@ -3071,3 +3093,74 @@ resource "azurerm_windows_virtual_machine_scale_set" "test" { } `, r.template(data), licenseType) } + +func (r WindowsVirtualMachineScaleSetResource) otherExtendedLocation(data acceptance.TestData, tag string) string { + return fmt.Sprintf(` +locals { + vm_name = "%s" +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + + // There is no supported extended location in "West Europe" + location = "westus" +} + +data "azurerm_extended_locations" "test" { + location = azurerm_resource_group.test.location +} + +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_prefixes = ["10.0.2.0/24"] +} + +resource "azurerm_windows_virtual_machine_scale_set" "test" { + name = local.vm_name + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "Standard_D2s_v3" + instances = 1 + admin_username = "adminuser" + admin_password = "P@ssword1234!" + extended_location = data.azurerm_extended_locations.test.extended_locations.0 + + source_image_reference { + publisher = "MicrosoftWindowsServer" + offer = "WindowsServer" + sku = "2019-Datacenter" + version = "latest" + } + + os_disk { + storage_account_type = "Premium_LRS" + caching = "ReadWrite" + } + + network_interface { + name = "example" + primary = true + + ip_configuration { + name = "internal" + primary = true + subnet_id = azurerm_subnet.test.id + } + } + + tags = { + ENV = "%s" + } +} +`, r.vmName(data), data.RandomInteger, data.RandomInteger, tag) +} diff --git a/internal/services/compute/windows_virtual_machine_scale_set_resource.go b/internal/services/compute/windows_virtual_machine_scale_set_resource.go index e9252b6e0aca..d0242cc32e29 100644 --- a/internal/services/compute/windows_virtual_machine_scale_set_resource.go +++ b/internal/services/compute/windows_virtual_machine_scale_set_resource.go @@ -151,6 +151,8 @@ func resourceWindowsVirtualMachineScaleSet() *pluginsdk.Resource { }, false), }, + "extended_location": azure.SchemaExtendedLocation(), + "extension": VirtualMachineScaleSetExtensionsSchema(), "extensions_time_budget": { @@ -613,6 +615,13 @@ func resourceWindowsVirtualMachineScaleSetCreate(d *pluginsdk.ResourceData, meta props.Zones = azure.ExpandZones(zonesRaw) } + if v, ok := d.GetOk("extended_location"); ok { + props.ExtendedLocation = &compute.ExtendedLocation{ + Name: utils.String(v.(string)), + Type: compute.ExtendedLocationTypesEdgeZone, + } + } + if v, ok := d.GetOk("platform_fault_domain_count"); ok { props.VirtualMachineScaleSetProperties.PlatformFaultDomainCount = utils.Int32(int32(v.(int))) } @@ -997,6 +1006,10 @@ func resourceWindowsVirtualMachineScaleSetRead(d *pluginsdk.ResourceData, meta i d.Set("location", location.NormalizeNilable(resp.Location)) d.Set("zones", zones.Flatten(resp.Zones)) + if resp.ExtendedLocation != nil && resp.ExtendedLocation.Name != nil { + d.Set("extended_location", resp.ExtendedLocation.Name) + } + var skuName *string var instances int if resp.Sku != nil { diff --git a/internal/services/loadbalancer/loadbalancer_resource.go b/internal/services/loadbalancer/loadbalancer_resource.go index fc64eac3aceb..db04138996f7 100644 --- a/internal/services/loadbalancer/loadbalancer_resource.go +++ b/internal/services/loadbalancer/loadbalancer_resource.go @@ -125,6 +125,13 @@ func resourceArmLoadBalancerCreateUpdate(d *pluginsdk.ResourceData, meta interfa LoadBalancerPropertiesFormat: &properties, } + if v, ok := d.GetOk("extended_location"); ok { + loadBalancer.ExtendedLocation = &network.ExtendedLocation{ + Name: utils.String(v.(string)), + Type: network.ExtendedLocationTypesEdgeZone, + } + } + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, loadBalancer) if err != nil { return fmt.Errorf("creating/updating %s: %+v", id, err) @@ -164,6 +171,10 @@ func resourceArmLoadBalancerRead(d *pluginsdk.ResourceData, meta interface{}) er d.Set("location", azure.NormalizeLocation(*location)) } + if resp.ExtendedLocation != nil && resp.ExtendedLocation.Name != nil { + d.Set("extended_location", resp.ExtendedLocation.Name) + } + if sku := resp.Sku; sku != nil { d.Set("sku", string(sku.Name)) d.Set("sku_tier", string(sku.Tier)) @@ -471,6 +482,8 @@ func resourceArmLoadBalancerSchema() map[string]*pluginsdk.Schema { }, false), }, + "extended_location": azure.SchemaExtendedLocation(), + "frontend_ip_configuration": { Type: pluginsdk.TypeList, Optional: true, diff --git a/internal/services/loadbalancer/loadbalancer_resource_test.go b/internal/services/loadbalancer/loadbalancer_resource_test.go index c8896242e170..89be46a5c0c7 100644 --- a/internal/services/loadbalancer/loadbalancer_resource_test.go +++ b/internal/services/loadbalancer/loadbalancer_resource_test.go @@ -311,6 +311,28 @@ func TestAccAzureRMLoadBalancer_PointToGatewayLB(t *testing.T) { }) } +func TestAccAzureRMLoadBalancer_extendedLocation(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_lb", "test") + r := LoadBalancer{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.extendedLocation(data, "Test1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.extendedLocation(data, "Test2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (r LoadBalancer) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { loadBalancerName := state.Attributes["name"] resourceGroup := state.Attributes["resource_group_name"] @@ -939,3 +961,33 @@ resource "azurerm_lb" "consumer" { } `, data.RandomInteger, data.Locations.Primary) } + +func (r LoadBalancer) extendedLocation(data acceptance.TestData, tag string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-lb-%d" + + // There is no supported extended location in "West Europe" + location = "westus" +} + +data "azurerm_extended_locations" "test" { + location = azurerm_resource_group.test.location +} + +resource "azurerm_lb" "test" { + name = "acctest-loadbalancer-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + extended_location = data.azurerm_extended_locations.test.extended_locations.0 + + tags = { + ENV = "%s" + } +} +`, data.RandomInteger, data.RandomInteger, tag) +} diff --git a/internal/services/network/network_interface_resource.go b/internal/services/network/network_interface_resource.go index 6a4e5b158128..9147372a7e5a 100644 --- a/internal/services/network/network_interface_resource.go +++ b/internal/services/network/network_interface_resource.go @@ -119,6 +119,8 @@ func resourceNetworkInterface() *pluginsdk.Resource { }, }, + "extended_location": azure.SchemaExtendedLocation(), + "dns_servers": { Type: pluginsdk.TypeList, Optional: true, @@ -267,6 +269,13 @@ func resourceNetworkInterfaceCreate(d *pluginsdk.ResourceData, meta interface{}) Tags: tags.Expand(t), } + if v, ok := d.GetOk("extended_location"); ok { + iface.ExtendedLocation = &network.ExtendedLocation{ + Name: utils.String(v.(string)), + Type: network.ExtendedLocationTypesEdgeZone, + } + } + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, iface) if err != nil { return fmt.Errorf("creating %s: %+v", id, err) @@ -308,8 +317,9 @@ func resourceNetworkInterfaceUpdate(d *pluginsdk.ResourceData, meta interface{}) location := azure.NormalizeLocation(d.Get("location").(string)) update := network.Interface{ - Name: utils.String(id.Name), - Location: utils.String(location), + Name: utils.String(id.Name), + Location: utils.String(location), + ExtendedLocation: existing.ExtendedLocation, InterfacePropertiesFormat: &network.InterfacePropertiesFormat{ EnableAcceleratedNetworking: utils.Bool(d.Get("enable_accelerated_networking").(bool)), DNSSettings: &network.InterfaceDNSSettings{}, @@ -405,6 +415,10 @@ func resourceNetworkInterfaceRead(d *pluginsdk.ResourceData, meta interface{}) e d.Set("location", azure.NormalizeLocation(*location)) } + if resp.ExtendedLocation != nil && resp.ExtendedLocation.Name != nil { + d.Set("extended_location", resp.ExtendedLocation.Name) + } + if props := resp.InterfacePropertiesFormat; props != nil { primaryPrivateIPAddress := "" privateIPAddresses := make([]interface{}, 0) diff --git a/internal/services/network/network_interface_resource_test.go b/internal/services/network/network_interface_resource_test.go index 665cb5d6aec7..6f14e1ba6c4c 100644 --- a/internal/services/network/network_interface_resource_test.go +++ b/internal/services/network/network_interface_resource_test.go @@ -324,6 +324,28 @@ func TestAccNetworkInterface_pointToGatewayLB(t *testing.T) { }) } +func TestAccNetworkInterface_extendedLocation(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_network_interface", "test") + r := NetworkInterfaceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.extendedLocation(data, "Test1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.extendedLocation(data, "Test2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (t NetworkInterfaceResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.NetworkInterfaceID(state.ID) if err != nil { @@ -833,3 +855,53 @@ resource "azurerm_subnet" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } + +func (r NetworkInterfaceResource) extendedLocation(data acceptance.TestData, tag string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + + // There is no supported extended location in "West Europe" + location = "westus" +} + +data "azurerm_extended_locations" "test" { + location = azurerm_resource_group.test.location +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvn-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + address_space = ["10.0.0.0/16"] +} + +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 = "acctestni-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + extended_location = data.azurerm_extended_locations.test.extended_locations.0 + + ip_configuration { + name = "primary" + subnet_id = azurerm_subnet.test.id + private_ip_address_allocation = "Dynamic" + } + + tags = { + ENV = "%s" + } +} +`, data.RandomInteger, data.RandomInteger, data.RandomInteger, tag) +} diff --git a/internal/services/network/public_ip_resource.go b/internal/services/network/public_ip_resource.go index dd64f516ad6f..8ac14fd000fd 100644 --- a/internal/services/network/public_ip_resource.go +++ b/internal/services/network/public_ip_resource.go @@ -66,6 +66,8 @@ func resourcePublicIp() *pluginsdk.Resource { }, false), }, + "extended_location": azure.SchemaExtendedLocation(), + "ip_version": { Type: pluginsdk.TypeString, Optional: true, @@ -291,6 +293,13 @@ func resourcePublicIpCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) e publicIp.Zones = zones } + if v, ok := d.GetOk("extended_location"); ok { + publicIp.ExtendedLocation = &network.ExtendedLocation{ + Name: utils.String(v.(string)), + Type: network.ExtendedLocationTypesEdgeZone, + } + } + publicIpPrefixId, publicIpPrefixIdOk := d.GetOk("public_ip_prefix_id") if publicIpPrefixIdOk { @@ -389,6 +398,10 @@ func resourcePublicIpRead(d *pluginsdk.ResourceData, meta interface{}) error { d.Set("zones", zonesDeprecated) } + if resp.ExtendedLocation != nil && resp.ExtendedLocation.Name != nil { + d.Set("extended_location", resp.ExtendedLocation.Name) + } + if sku := resp.Sku; sku != nil { d.Set("sku", string(sku.Name)) d.Set("sku_tier", string(sku.Tier)) diff --git a/internal/services/network/public_ip_resource_test.go b/internal/services/network/public_ip_resource_test.go index 998730544a5c..90871fb750d2 100644 --- a/internal/services/network/public_ip_resource_test.go +++ b/internal/services/network/public_ip_resource_test.go @@ -521,6 +521,28 @@ func TestAccPublicIpStatic_regionalTier(t *testing.T) { }) } +func TestAccPublicIpStatic_extendedLocation(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") + r := PublicIPResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.extendedLocation(data, "Test1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.extendedLocation(data, "Test2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (t PublicIPResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.PublicIpAddressID(state.ID) if err != nil { @@ -1053,3 +1075,35 @@ resource "azurerm_public_ip" "test" { } `, data.RandomInteger, data.Locations.Primary) } + +func (PublicIPResource) extendedLocation(data acceptance.TestData, tag string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + // There is no supported extended location in "West Europe" + location = "westus" +} + +data "azurerm_extended_locations" "test" { + location = azurerm_resource_group.test.location +} + +resource "azurerm_public_ip" "test" { + name = "acctestpublicip-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + allocation_method = "Static" + sku = "Standard" + availability_zone = "No-Zone" + extended_location = data.azurerm_extended_locations.test.extended_locations.0 + + tags = { + ENV = "%s" + } +} +`, data.RandomInteger, data.RandomInteger, tag) +} diff --git a/internal/services/network/virtual_network_gateway_resource.go b/internal/services/network/virtual_network_gateway_resource.go index 0aad9d44fe37..a0a69e3d5a08 100644 --- a/internal/services/network/virtual_network_gateway_resource.go +++ b/internal/services/network/virtual_network_gateway_resource.go @@ -79,6 +79,8 @@ func resourceVirtualNetworkGatewaySchema() map[string]*pluginsdk.Schema { }, !features.ThreePointOhBeta()), }, + "extended_location": azure.SchemaExtendedLocation(), + // TODO 4.0: change this from enable_* to *_enabled "enable_bgp": { Type: pluginsdk.TypeBool, @@ -463,6 +465,13 @@ func resourceVirtualNetworkGatewayCreateUpdate(d *pluginsdk.ResourceData, meta i VirtualNetworkGatewayPropertiesFormat: properties, } + if v, ok := d.GetOk("extended_location"); ok { + gateway.ExtendedLocation = &network.ExtendedLocation{ + Name: utils.String(v.(string)), + Type: network.ExtendedLocationTypesEdgeZone, + } + } + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, gateway) if err != nil { return fmt.Errorf("Creating/Updating %s: %+v", id, err) @@ -502,6 +511,10 @@ func resourceVirtualNetworkGatewayRead(d *pluginsdk.ResourceData, meta interface d.Set("location", azure.NormalizeLocation(*location)) } + if resp.ExtendedLocation != nil && resp.ExtendedLocation.Name != nil { + d.Set("extended_location", resp.ExtendedLocation.Name) + } + if gw := resp.VirtualNetworkGatewayPropertiesFormat; gw != nil { d.Set("type", string(gw.GatewayType)) d.Set("enable_bgp", gw.EnableBgp) diff --git a/internal/services/network/virtual_network_gateway_resource_test.go b/internal/services/network/virtual_network_gateway_resource_test.go index 4b700f304b2b..4c0cc3455dca 100644 --- a/internal/services/network/virtual_network_gateway_resource_test.go +++ b/internal/services/network/virtual_network_gateway_resource_test.go @@ -357,6 +357,28 @@ func TestAccVirtualNetworkGateway_customRoute(t *testing.T) { }) } +func TestAccVirtualNetworkGateway_extendedLocation(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_network_gateway", "test") + r := VirtualNetworkGatewayResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.extendedLocation(data, "Test1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.extendedLocation(data, "Test2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (t VirtualNetworkGatewayResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { gatewayName := state.Attributes["name"] resourceGroup := state.Attributes["resource_group_name"] @@ -1539,3 +1561,64 @@ resource "azurerm_virtual_network_gateway" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.Client().TenantID, data.Client().TenantID) } + +func (VirtualNetworkGatewayResource) extendedLocation(data acceptance.TestData, tag string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + + // There is no supported extended location in "West Europe" + location = "westus" +} + +data "azurerm_extended_locations" "test" { + location = azurerm_resource_group.test.location +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvn-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + address_space = ["10.0.0.0/16"] +} + +resource "azurerm_subnet" "test" { + name = "GatewaySubnet" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefix = "10.0.1.0/24" +} + +resource "azurerm_public_ip" "test" { + name = "acctestpip-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + allocation_method = "Dynamic" +} + +resource "azurerm_virtual_network_gateway" "test" { + name = "acctestvng-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + extended_location = data.azurerm_extended_locations.test.extended_locations.0 + + type = "Vpn" + vpn_type = "RouteBased" + sku = "Standard" + + ip_configuration { + public_ip_address_id = azurerm_public_ip.test.id + private_ip_address_allocation = "Dynamic" + subnet_id = azurerm_subnet.test.id + } + + tags = { + ENV = "%s" + } +} +`, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, tag) +} diff --git a/internal/services/network/virtual_network_resource.go b/internal/services/network/virtual_network_resource.go index 4bf12206407c..ae5188cd7570 100644 --- a/internal/services/network/virtual_network_resource.go +++ b/internal/services/network/virtual_network_resource.go @@ -70,6 +70,8 @@ func resourceVirtualNetworkSchema() map[string]*pluginsdk.Schema { }, }, + "extended_location": azure.SchemaExtendedLocation(), + "bgp_community": { Type: pluginsdk.TypeString, Optional: true, @@ -200,6 +202,13 @@ func resourceVirtualNetworkCreateUpdate(d *pluginsdk.ResourceData, meta interfac Tags: tags.Expand(t), } + if v, ok := d.GetOk("extended_location"); ok { + vnet.ExtendedLocation = &network.ExtendedLocation{ + Name: utils.String(v.(string)), + Type: network.ExtendedLocationTypesEdgeZone, + } + } + if v, ok := d.GetOk("flow_timeout_in_minutes"); ok { vnet.VirtualNetworkPropertiesFormat.FlowTimeoutInMinutes = utils.Int32(int32(v.(int))) } @@ -273,6 +282,10 @@ func resourceVirtualNetworkRead(d *pluginsdk.ResourceData, meta interface{}) err d.Set("location", azure.NormalizeLocation(*location)) } + if resp.ExtendedLocation != nil && resp.ExtendedLocation.Name != nil { + d.Set("extended_location", resp.ExtendedLocation.Name) + } + if props := resp.VirtualNetworkPropertiesFormat; props != nil { d.Set("guid", props.ResourceGUID) d.Set("flow_timeout_in_minutes", props.FlowTimeoutInMinutes) diff --git a/internal/services/network/virtual_network_resource_test.go b/internal/services/network/virtual_network_resource_test.go index 097cc4e8f7e7..b453ade07da4 100644 --- a/internal/services/network/virtual_network_resource_test.go +++ b/internal/services/network/virtual_network_resource_test.go @@ -237,6 +237,28 @@ func TestAccVirtualNetwork_bgpCommunity(t *testing.T) { }) } +func TestAccVirtualNetwork_extendedLocation(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_network", "test") + r := VirtualNetworkResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.extendedLocation(data, "Test1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.extendedLocation(data, "Test2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (t VirtualNetworkResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.VirtualNetworkID(state.ID) if err != nil { @@ -506,3 +528,39 @@ resource "azurerm_virtual_network" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, flowTimeout) } + +func (VirtualNetworkResource) extendedLocation(data acceptance.TestData, tag string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + + // There is no supported extended location in "West Europe" + location = "westus" +} + +data "azurerm_extended_locations" "test" { + location = azurerm_resource_group.test.location +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + extended_location = data.azurerm_extended_locations.test.extended_locations.0 + + subnet { + name = "subnet1" + address_prefix = "10.0.1.0/24" + } + + tags = { + ENV = "%s" + } +} +`, data.RandomInteger, data.RandomInteger, tag) +} diff --git a/internal/services/storage/storage_account_resource.go b/internal/services/storage/storage_account_resource.go index 03016b7d784c..de190f7df878 100644 --- a/internal/services/storage/storage_account_resource.go +++ b/internal/services/storage/storage_account_resource.go @@ -266,6 +266,8 @@ func resourceStorageAccount() *pluginsdk.Resource { Default: true, }, + "extended_location": azure.SchemaExtendedLocation(), + "min_tls_version": { Type: pluginsdk.TypeString, Optional: true, @@ -1046,6 +1048,13 @@ func resourceStorageAccountCreate(d *pluginsdk.ResourceData, meta interface{}) e }, } + if v, ok := d.GetOk("extended_location"); ok { + parameters.ExtendedLocation = &storage.ExtendedLocation{ + Name: utils.String(v.(string)), + Type: storage.ExtendedLocationTypesEdgeZone, + } + } + // For all Clouds except Public, China, and USGovernmentCloud, don't specify "allow_blob_public_access" and "min_tls_version" in request body. // https://github.com/hashicorp/terraform-provider-azurerm/issues/7812 // https://github.com/hashicorp/terraform-provider-azurerm/issues/8083 @@ -1753,6 +1762,9 @@ func resourceStorageAccountRead(d *pluginsdk.ResourceData, meta interface{}) err if location := resp.Location; location != nil { d.Set("location", azure.NormalizeLocation(*location)) } + if resp.ExtendedLocation != nil && resp.ExtendedLocation.Name != nil { + d.Set("extended_location", resp.ExtendedLocation.Name) + } d.Set("account_kind", resp.Kind) if sku := resp.Sku; sku != nil { diff --git a/internal/services/storage/storage_account_resource_test.go b/internal/services/storage/storage_account_resource_test.go index 8212d4a1d819..f75fd6b7985b 100644 --- a/internal/services/storage/storage_account_resource_test.go +++ b/internal/services/storage/storage_account_resource_test.go @@ -1175,6 +1175,28 @@ func TestAccStorageAccount_customerManagedKeyRemoteKeyVault(t *testing.T) { }) } +func TestAccStorageAccount_extendedLocation(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_storage_account", "test") + r := StorageAccountResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.extendedLocation(data, "Test1"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.extendedLocation(data, "Test2"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (r StorageAccountResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.StorageAccountID(state.ID) if err != nil { @@ -3739,3 +3761,33 @@ resource "azurerm_storage_account" "test" { } `, clientData.SubscriptionIDAlt, clientData.TenantID, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomString, clientData.TenantID, data.RandomInteger, data.Locations.Primary, data.RandomString) } + +func (r StorageAccountResource) extendedLocation(data acceptance.TestData, tag string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-storage-%d" + // There is no supported extended location in "West Europe" + location = "westus" +} + +data "azurerm_extended_locations" "test" { + location = azurerm_resource_group.test.location +} + +resource "azurerm_storage_account" "test" { + name = "unlikely23exst2acct%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Premium" + account_replication_type = "LRS" + extended_location = data.azurerm_extended_locations.test.extended_locations.0 + tags = { + environment = "%s" + } +} +`, data.RandomInteger, data.RandomString, tag) +} diff --git a/website/docs/r/lb.html.markdown b/website/docs/r/lb.html.markdown index c56f56dff6e1..9b580a4252ab 100644 --- a/website/docs/r/lb.html.markdown +++ b/website/docs/r/lb.html.markdown @@ -44,6 +44,7 @@ The following arguments are supported: * `name` - (Required) Specifies the name of the Load Balancer. * `resource_group_name` - (Required) The name of the Resource Group in which to create the Load Balancer. * `location` - (Required) Specifies the supported Azure Region where the Load Balancer should be created. +* `extended_location` - (Optional) Specifies the supported Azure extended location where the resource exists. It allows customers to take advantage of more granular locations. Changing this forces a new resource to be created. * `frontend_ip_configuration` - (Optional) One or multiple `frontend_ip_configuration` blocks as documented below. * `sku` - (Optional) The SKU of the Azure Load Balancer. Accepted values are `Basic`, `Standard` and `Gateway`. Defaults to `Basic`. diff --git a/website/docs/r/linux_virtual_machine.html.markdown b/website/docs/r/linux_virtual_machine.html.markdown index 08bfa60e9387..837b4daa0f70 100644 --- a/website/docs/r/linux_virtual_machine.html.markdown +++ b/website/docs/r/linux_virtual_machine.html.markdown @@ -150,6 +150,8 @@ The following arguments are supported: -> **NOTE:** This can only be configured when `priority` is set to `Spot`. +* `extended_location` - (Optional) Specifies the supported Azure extended location where the resource exists. It allows customers to take advantage of more granular locations. Changing this forces a new resource to be created. + * `extensions_time_budget` - (Optional) Specifies the duration allocated for all extensions to start. The time duration should be between 15 minutes and 120 minutes (inclusive) and should be specified in ISO 8601 format. Defaults to 90 minutes (`PT1H30M`). * `identity` - (Optional) An `identity` block as defined below. diff --git a/website/docs/r/linux_virtual_machine_scale_set.html.markdown b/website/docs/r/linux_virtual_machine_scale_set.html.markdown index dcc9cc8b52c3..7969d5ea57b1 100644 --- a/website/docs/r/linux_virtual_machine_scale_set.html.markdown +++ b/website/docs/r/linux_virtual_machine_scale_set.html.markdown @@ -146,6 +146,8 @@ The following arguments are supported: * `encryption_at_host_enabled` - (Optional) Should all of the disks (including the temp disk) attached to this Virtual Machine be encrypted by enabling Encryption at Host? +* `extended_location` - (Optional) Specifies the supported Azure extended location where the resource exists. It allows customers to take advantage of more granular locations. Changing this forces a new resource to be created. + * `extension` - (Optional) One or more `extension` blocks as defined below * `extensions_time_budget` - (Optional) Specifies the duration allocated for all extensions to start. The time duration should be between `15` minutes and `120` minutes (inclusive) and should be specified in ISO 8601 format. Defaults to `90` minutes (`PT1H30M`). diff --git a/website/docs/r/managed_disk.html.markdown b/website/docs/r/managed_disk.html.markdown index 719ff156fb5d..2f38977764a7 100644 --- a/website/docs/r/managed_disk.html.markdown +++ b/website/docs/r/managed_disk.html.markdown @@ -111,6 +111,8 @@ The following arguments are supported: * `encryption_settings` - (Optional) A `encryption_settings` block as defined below. +* `extended_location` - (Optional) Specifies the supported Azure extended location where the resource exists. It allows customers to take advantage of more granular locations. Changing this forces a new resource to be created. + * `hyper_v_generation` - (Optional) The HyperV Generation of the Disk when the source of an `Import` or `Copy` operation targets a source that contains an operating system. Possible values are `V1` and `V2`. Changing this forces a new resource to be created. * `image_reference_id` - (Optional) ID of an existing platform/marketplace disk image to copy when `create_option` is `FromImage`. This field cannot be specified if gallery_image_reference_id is specified. diff --git a/website/docs/r/network_interface.html.markdown b/website/docs/r/network_interface.html.markdown index 193cf316dc41..2a1fe1298203 100644 --- a/website/docs/r/network_interface.html.markdown +++ b/website/docs/r/network_interface.html.markdown @@ -60,6 +60,8 @@ The following arguments are supported: --- +* `extended_location` - (Optional) Specifies the supported Azure extended location where the resource exists. It allows customers to take advantage of more granular locations. Changing this forces a new resource to be created. + * `dns_servers` - (Optional) A list of IP Addresses defining the DNS Servers which should be used for this Network Interface. -> **Note:** Configuring DNS Servers on the Network Interface will override the DNS Servers defined on the Virtual Network. diff --git a/website/docs/r/public_ip.html.markdown b/website/docs/r/public_ip.html.markdown index 796fc1b0693e..efa24412c696 100644 --- a/website/docs/r/public_ip.html.markdown +++ b/website/docs/r/public_ip.html.markdown @@ -42,6 +42,8 @@ The following arguments are supported: * `location` - (Required) Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. +* `extended_location` - (Optional) Specifies the supported Azure extended location where the resource exists. It allows customers to take advantage of more granular locations. Changing this forces a new resource to be created. + * `sku` - (Optional) The SKU of the Public IP. Accepted values are `Basic` and `Standard`. Defaults to `Basic`. -> **Note** Public IP Standard SKUs require `allocation_method` to be set to `Static`. diff --git a/website/docs/r/storage_account.html.markdown b/website/docs/r/storage_account.html.markdown index 28b9f017cd14..94f5182106cf 100644 --- a/website/docs/r/storage_account.html.markdown +++ b/website/docs/r/storage_account.html.markdown @@ -99,6 +99,8 @@ The following arguments are supported: * `enable_https_traffic_only` - (Optional) Boolean flag which forces HTTPS if enabled, see [here](https://docs.microsoft.com/en-us/azure/storage/storage-require-secure-transfer/) for more information. Defaults to `true`. +* `extended_location` - (Optional) Specifies the supported Azure extended location where the resource exists. It allows customers to take advantage of more granular locations. Changing this forces a new resource to be created. + * `min_tls_version` - (Optional) The minimum supported TLS version for the storage account. Possible values are `TLS1_0`, `TLS1_1`, and `TLS1_2`. Defaults to `TLS1_0` for new storage accounts. -> **NOTE:** At this time `min_tls_version` is only supported in the Public Cloud, China Cloud, and US Government Cloud. diff --git a/website/docs/r/virtual_network.html.markdown b/website/docs/r/virtual_network.html.markdown index 917ff9cd7158..6a70e53613e9 100644 --- a/website/docs/r/virtual_network.html.markdown +++ b/website/docs/r/virtual_network.html.markdown @@ -67,6 +67,8 @@ The following arguments are supported: * `location` - (Required) The location/region where the virtual network is created. Changing this forces a new resource to be created. +* `extended_location` - (Optional) Specifies the supported Azure extended location where the resource exists. It allows customers to take advantage of more granular locations. Changing this forces a new resource to be created. + * `bgp_community` - (Optional) The BGP community attribute in format `:`. -> **NOTE** The `as-number` segment is the Microsoft ASN, which is always `12076` for now. diff --git a/website/docs/r/virtual_network_gateway.html.markdown b/website/docs/r/virtual_network_gateway.html.markdown index 3334b8904f1a..776dfcb210fe 100644 --- a/website/docs/r/virtual_network_gateway.html.markdown +++ b/website/docs/r/virtual_network_gateway.html.markdown @@ -120,6 +120,8 @@ The following arguments are supported: * `vpn_type` - (Optional) The routing type of the Virtual Network Gateway. Valid options are `RouteBased` or `PolicyBased`. Defaults to `RouteBased`. +* `extended_location` - (Optional) Specifies the supported Azure extended location where the resource exists. It allows customers to take advantage of more granular locations. Changing this forces a new resource to be created. + * `enable_bgp` - (Optional) If `true`, BGP (Border Gateway Protocol) will be enabled for this Virtual Network Gateway. Defaults to `false`. diff --git a/website/docs/r/windows_virtual_machine.html.markdown b/website/docs/r/windows_virtual_machine.html.markdown index 2f39c9eb0e92..eda696621430 100644 --- a/website/docs/r/windows_virtual_machine.html.markdown +++ b/website/docs/r/windows_virtual_machine.html.markdown @@ -135,6 +135,8 @@ The following arguments are supported: -> **NOTE:** This can only be configured when `priority` is set to `Spot`. +* `extended_location` - (Optional) Specifies the supported Azure extended location where the resource exists. It allows customers to take advantage of more granular locations. Changing this forces a new resource to be created. + * `extensions_time_budget` - (Optional) Specifies the duration allocated for all extensions to start. The time duration should be between 15 minutes and 120 minutes (inclusive) and should be specified in ISO 8601 format. Defaults to 90 minutes (`PT1H30M`). * `hotpatching_enabled` - (Optional) Should the VM be patched without requiring a reboot? Possible values are `true` or `false`. Defaults to `false`. For more information about hot patching please see the [product documentation](https://docs.microsoft.com/azure/automanage/automanage-hotpatch). diff --git a/website/docs/r/windows_virtual_machine_scale_set.html.markdown b/website/docs/r/windows_virtual_machine_scale_set.html.markdown index ce2a872d5cd9..a784f5f6c467 100644 --- a/website/docs/r/windows_virtual_machine_scale_set.html.markdown +++ b/website/docs/r/windows_virtual_machine_scale_set.html.markdown @@ -134,6 +134,8 @@ The following arguments are supported: * `encryption_at_host_enabled` - (Optional) Should all of the disks (including the temp disk) attached to this Virtual Machine be encrypted by enabling Encryption at Host? +* `extended_location` - (Optional) Specifies the supported Azure extended location where the resource exists. It allows customers to take advantage of more granular locations. Changing this forces a new resource to be created. + * `extension` - (Optional) One or more `extension` blocks as defined below * `extensions_time_budget` - (Optional) Specifies the duration allocated for all extensions to start. The time duration should be between `15` minutes and `120` minutes (inclusive) and should be specified in ISO 8601 format. Defaults to `90` minutes (`PT1H30M`).