From 5e0cac456d2294a8a09f1da3329c9cc1cffef420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cneil-yechenwei=E2=80=9D?= <“yechenwei2007@hotmail.com”> Date: Wed, 26 Nov 2025 11:55:46 +0800 Subject: [PATCH 01/17] azurerm_nat_gateway/azurerm_public_ip/azurerm_public_ip_prefix - support for new SKU StandardV2 --- .../services/network/nat_gateway_resource.go | 10 +++--- .../network/nat_gateway_resource_test.go | 36 +++++++++++++++++++ .../network/public_ip_prefix_resource.go | 12 +++---- .../network/public_ip_prefix_resource_test.go | 1 + .../services/network/public_ip_resource.go | 5 +-- .../network/public_ip_resource_test.go | 8 ++--- website/docs/r/nat_gateway.html.markdown | 2 +- website/docs/r/public_ip.html.markdown | 2 +- website/docs/r/public_ip_prefix.html.markdown | 2 +- 9 files changed, 54 insertions(+), 24 deletions(-) diff --git a/internal/services/network/nat_gateway_resource.go b/internal/services/network/nat_gateway_resource.go index 97b4da5f76c5..f0497c2347cb 100644 --- a/internal/services/network/nat_gateway_resource.go +++ b/internal/services/network/nat_gateway_resource.go @@ -74,12 +74,10 @@ func resourceNatGatewaySchema() map[string]*pluginsdk.Schema { }, "sku_name": { - Type: pluginsdk.TypeString, - Optional: true, - Default: string(natgateways.NatGatewaySkuNameStandard), - ValidateFunc: validation.StringInSlice([]string{ - string(natgateways.NatGatewaySkuNameStandard), - }, false), + Type: pluginsdk.TypeString, + Optional: true, + Default: string(natgateways.NatGatewaySkuNameStandard), + ValidateFunc: validation.StringInSlice(natgateways.PossibleValuesForNatGatewaySkuName(), false), }, "zones": commonschema.ZonesMultipleOptionalForceNew(), diff --git a/internal/services/network/nat_gateway_resource_test.go b/internal/services/network/nat_gateway_resource_test.go index b450a9712f05..22f11ef78a02 100644 --- a/internal/services/network/nat_gateway_resource_test.go +++ b/internal/services/network/nat_gateway_resource_test.go @@ -75,6 +75,21 @@ func TestAccNatGateway_update(t *testing.T) { }) } +func TestAccNatGateway_standardVTwo(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_nat_gateway", "test") + r := NatGatewayResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.standardVTwo(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (t NatGatewayResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := natgateways.ParseNatGatewayID(state.ID) if err != nil { @@ -208,3 +223,24 @@ resource "azurerm_nat_gateway_public_ip_prefix_association" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) } + +func (NatGatewayResource) standardVTwo(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-network-%d" + location = "%s" +} + +resource "azurerm_nat_gateway" "test" { + name = "acctestnatGateway-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku_name = "StandardV2" + zones = ["1", "2", "3"] +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} diff --git a/internal/services/network/public_ip_prefix_resource.go b/internal/services/network/public_ip_prefix_resource.go index 1a66ea210dbf..a5e3f4e7a76c 100644 --- a/internal/services/network/public_ip_prefix_resource.go +++ b/internal/services/network/public_ip_prefix_resource.go @@ -65,13 +65,11 @@ func resourcePublicIpPrefix() *pluginsdk.Resource { }, "sku": { - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - Default: string(publicipprefixes.PublicIPPrefixSkuNameStandard), - ValidateFunc: validation.StringInSlice([]string{ - string(publicipprefixes.PublicIPPrefixSkuNameStandard), - }, false), + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + Default: string(publicipprefixes.PublicIPPrefixSkuNameStandard), + ValidateFunc: validation.StringInSlice(publicipprefixes.PossibleValuesForPublicIPPrefixSkuName(), false), }, "sku_tier": { diff --git a/internal/services/network/public_ip_prefix_resource_test.go b/internal/services/network/public_ip_prefix_resource_test.go index a4ec3e0a4dda..1c166ab18e5b 100644 --- a/internal/services/network/public_ip_prefix_resource_test.go +++ b/internal/services/network/public_ip_prefix_resource_test.go @@ -410,6 +410,7 @@ resource "azurerm_public_ip_prefix" "test" { name = "acctestpublicipprefix-%d" location = azurerm_resource_group.test.location sku_tier = "%s" + sku = "StandardV2" } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, tier) } diff --git a/internal/services/network/public_ip_resource.go b/internal/services/network/public_ip_resource.go index de5c7052863b..62ac785ed965 100644 --- a/internal/services/network/public_ip_resource.go +++ b/internal/services/network/public_ip_resource.go @@ -107,10 +107,7 @@ func resourcePublicIp() *pluginsdk.Resource { ForceNew: true, Default: string(publicipaddresses.PublicIPAddressSkuNameStandard), // https://azure.microsoft.com/en-us/updates/upgrade-to-standard-sku-public-ip-addresses-in-azure-by-30-september-2025-basic-sku-will-be-retired/ - ValidateFunc: validation.StringInSlice([]string{ - string(publicipaddresses.PublicIPAddressSkuNameBasic), - string(publicipaddresses.PublicIPAddressSkuNameStandard), - }, false), + ValidateFunc: validation.StringInSlice(publicipaddresses.PossibleValuesForPublicIPAddressSkuName(), false), }, "sku_tier": { diff --git a/internal/services/network/public_ip_resource_test.go b/internal/services/network/public_ip_resource_test.go index 44e3163c1d1a..43fe08073f2c 100644 --- a/internal/services/network/public_ip_resource_test.go +++ b/internal/services/network/public_ip_resource_test.go @@ -180,13 +180,13 @@ func TestAccPublicIpStatic_basic_withIPv4(t *testing.T) { }) } -func TestAccPublicIpStatic_standard(t *testing.T) { +func TestAccPublicIpStatic_standardVTwo(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIPResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.standard(data), + Config: r.standardVTwo(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -547,7 +547,7 @@ resource "azurerm_public_ip" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, ipVersion) } -func (PublicIPResource) standard(data acceptance.TestData) string { +func (PublicIPResource) standardVTwo(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -563,7 +563,7 @@ resource "azurerm_public_ip" "test" { location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name allocation_method = "Static" - sku = "Standard" + sku = "StandardV2" } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } diff --git a/website/docs/r/nat_gateway.html.markdown b/website/docs/r/nat_gateway.html.markdown index 9e3d62cda4b5..abe9b6506e26 100644 --- a/website/docs/r/nat_gateway.html.markdown +++ b/website/docs/r/nat_gateway.html.markdown @@ -41,7 +41,7 @@ The following arguments are supported: * `idle_timeout_in_minutes` - (Optional) The idle timeout which should be used in minutes. Defaults to `4`. -* `sku_name` - (Optional) The SKU which should be used. At this time the only supported value is `Standard`. Defaults to `Standard`. +* `sku_name` - (Optional) The SKU which should be used. Possible values are `Standard` and `StandardV2`. Defaults to `Standard`. * `tags` - (Optional) A mapping of tags to assign to the resource. diff --git a/website/docs/r/public_ip.html.markdown b/website/docs/r/public_ip.html.markdown index 4d958444bf87..86b1f109e4fc 100644 --- a/website/docs/r/public_ip.html.markdown +++ b/website/docs/r/public_ip.html.markdown @@ -78,7 +78,7 @@ The following arguments are supported: * `reverse_fqdn` - (Optional) A fully qualified domain name that resolves to this public IP address. If the reverseFqdn is specified, then a PTR DNS record is created pointing from the IP address in the in-addr.arpa domain to the reverse FQDN. -* `sku` - (Optional) The SKU of the Public IP. Accepted values are `Basic` and `Standard`. Defaults to `Standard`. Changing this forces a new resource to be created. +* `sku` - (Optional) The SKU of the Public IP. Accepted values are `Basic`, `Standard` and `StandardV2`. Defaults to `Standard`. Changing this forces a new resource to be created. -> **Note:** Public IP Standard SKUs require `allocation_method` to be set to `Static`. diff --git a/website/docs/r/public_ip_prefix.html.markdown b/website/docs/r/public_ip_prefix.html.markdown index c19e163487c1..ab0037e7c0b2 100644 --- a/website/docs/r/public_ip_prefix.html.markdown +++ b/website/docs/r/public_ip_prefix.html.markdown @@ -45,7 +45,7 @@ The following arguments are supported: -> **Note:** When `ip_version` is set to `IPv6`, `custom_ip_prefix_id` must reference a regional (child) range rather than a global (parent) range. For more details on creating a Public IP Prefix from a custom IP prefix, see [here](https://learn.microsoft.com/en-us/azure/virtual-network/ip-services/manage-custom-ip-address-prefix#create-a-public-ip-prefix-from-a-custom-ip-prefix). -* `sku` - (Optional) The SKU of the Public IP Prefix. Accepted values are `Standard`. Defaults to `Standard`. Changing this forces a new resource to be created. +* `sku` - (Optional) The SKU of the Public IP Prefix. Accepted values are `Standard` and `StandardV2`. Defaults to `Standard`. Changing this forces a new resource to be created. -> **Note:** Public IP Prefix can only be created with Standard SKUs at this time. From 86691866c7d4e88eb9ac0bf943dff76d1cbdafb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cneil-yechenwei=E2=80=9D?= <“yechenwei2007@hotmail.com”> Date: Wed, 26 Nov 2025 11:59:33 +0800 Subject: [PATCH 02/17] update md file --- website/docs/r/public_ip.html.markdown | 2 +- website/docs/r/public_ip_prefix.html.markdown | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/public_ip.html.markdown b/website/docs/r/public_ip.html.markdown index 86b1f109e4fc..dfd7c5d29f5b 100644 --- a/website/docs/r/public_ip.html.markdown +++ b/website/docs/r/public_ip.html.markdown @@ -78,7 +78,7 @@ The following arguments are supported: * `reverse_fqdn` - (Optional) A fully qualified domain name that resolves to this public IP address. If the reverseFqdn is specified, then a PTR DNS record is created pointing from the IP address in the in-addr.arpa domain to the reverse FQDN. -* `sku` - (Optional) The SKU of the Public IP. Accepted values are `Basic`, `Standard` and `StandardV2`. Defaults to `Standard`. Changing this forces a new resource to be created. +* `sku` - (Optional) The SKU of the Public IP. Possible values are `Basic`, `Standard` and `StandardV2`. Defaults to `Standard`. Changing this forces a new resource to be created. -> **Note:** Public IP Standard SKUs require `allocation_method` to be set to `Static`. diff --git a/website/docs/r/public_ip_prefix.html.markdown b/website/docs/r/public_ip_prefix.html.markdown index ab0037e7c0b2..06cb1b1f5e8a 100644 --- a/website/docs/r/public_ip_prefix.html.markdown +++ b/website/docs/r/public_ip_prefix.html.markdown @@ -45,7 +45,7 @@ The following arguments are supported: -> **Note:** When `ip_version` is set to `IPv6`, `custom_ip_prefix_id` must reference a regional (child) range rather than a global (parent) range. For more details on creating a Public IP Prefix from a custom IP prefix, see [here](https://learn.microsoft.com/en-us/azure/virtual-network/ip-services/manage-custom-ip-address-prefix#create-a-public-ip-prefix-from-a-custom-ip-prefix). -* `sku` - (Optional) The SKU of the Public IP Prefix. Accepted values are `Standard` and `StandardV2`. Defaults to `Standard`. Changing this forces a new resource to be created. +* `sku` - (Optional) The SKU of the Public IP Prefix. Possible values are `Standard` and `StandardV2`. Defaults to `Standard`. Changing this forces a new resource to be created. -> **Note:** Public IP Prefix can only be created with Standard SKUs at this time. From 1ca4e100481b62fd3251c224bc5d4f5da88b0d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cneil-yechenwei=E2=80=9D?= <“yechenwei2007@hotmail.com”> Date: Thu, 4 Dec 2025 10:17:44 +0800 Subject: [PATCH 03/17] update md --- website/docs/r/nat_gateway.html.markdown | 2 ++ website/docs/r/public_ip.html.markdown | 2 ++ 2 files changed, 4 insertions(+) diff --git a/website/docs/r/nat_gateway.html.markdown b/website/docs/r/nat_gateway.html.markdown index abe9b6506e26..b6659d0ae8da 100644 --- a/website/docs/r/nat_gateway.html.markdown +++ b/website/docs/r/nat_gateway.html.markdown @@ -43,6 +43,8 @@ The following arguments are supported: * `sku_name` - (Optional) The SKU which should be used. Possible values are `Standard` and `StandardV2`. Defaults to `Standard`. +-> **Note:** When `StandardV2` is enabled, service API will also enable `zones`. The user has to explicitly set this property in the Terraform configuration or handle it using `ignore_changes`. + * `tags` - (Optional) A mapping of tags to assign to the resource. * `zones` - (Optional) A list of Availability Zones in which this NAT Gateway should be located. Changing this forces a new NAT Gateway to be created. diff --git a/website/docs/r/public_ip.html.markdown b/website/docs/r/public_ip.html.markdown index dfd7c5d29f5b..289c0043b5fe 100644 --- a/website/docs/r/public_ip.html.markdown +++ b/website/docs/r/public_ip.html.markdown @@ -82,6 +82,8 @@ The following arguments are supported: -> **Note:** Public IP Standard SKUs require `allocation_method` to be set to `Static`. +-> **Note:** `Basic` will be deprecated. [Read more](https://azure.microsoft.com/en-us/updates/upgrade-to-standard-sku-public-ip-addresses-in-azure-by-30-september-2025-basic-sku-will-be-retired/). + * `sku_tier` - (Optional) The SKU Tier that should be used for the Public IP. Possible values are `Regional` and `Global`. Defaults to `Regional`. Changing this forces a new resource to be created. -> **Note:** When `sku_tier` is set to `Global`, `sku` must be set to `Standard`. From c1dcf3cf665139cfb21ace6594ede28a741922fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cneil-yechenwei=E2=80=9D?= <“yechenwei2007@hotmail.com”> Date: Mon, 8 Dec 2025 09:15:08 +0800 Subject: [PATCH 04/17] update md file --- website/docs/r/nat_gateway.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/nat_gateway.html.markdown b/website/docs/r/nat_gateway.html.markdown index b6659d0ae8da..893084c35afe 100644 --- a/website/docs/r/nat_gateway.html.markdown +++ b/website/docs/r/nat_gateway.html.markdown @@ -43,7 +43,7 @@ The following arguments are supported: * `sku_name` - (Optional) The SKU which should be used. Possible values are `Standard` and `StandardV2`. Defaults to `Standard`. --> **Note:** When `StandardV2` is enabled, service API will also enable `zones`. The user has to explicitly set this property in the Terraform configuration or handle it using `ignore_changes`. +-> **Note:** When `StandardV2` is enabled, service API will also enable all availability zones. See [MS learn documentation](https://learn.microsoft.com/en-us/azure/nat-gateway/nat-overview#standardv2-nat-gateway) for more info. The user has to explicitly set this property in the Terraform configuration or handle it using `ignore_changes`. * `tags` - (Optional) A mapping of tags to assign to the resource. From e88277069ab8f070a4b5f1356e02113cbe87826a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cneil-yechenwei=E2=80=9D?= <“yechenwei2007@hotmail.com”> Date: Mon, 8 Dec 2025 11:48:43 +0800 Subject: [PATCH 05/17] Add CustomizeDiff Validtor for new sku StandardV2 --- .../services/network/nat_gateway_resource.go | 27 +++++++++++++++++++ website/docs/r/nat_gateway.html.markdown | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/internal/services/network/nat_gateway_resource.go b/internal/services/network/nat_gateway_resource.go index f0497c2347cb..424ed9a61c76 100644 --- a/internal/services/network/nat_gateway_resource.go +++ b/internal/services/network/nat_gateway_resource.go @@ -4,8 +4,10 @@ package network import ( + "context" "fmt" "log" + "sort" "time" "github.com/hashicorp/go-azure-helpers/lang/pointer" @@ -50,6 +52,31 @@ func resourceNatGateway() *pluginsdk.Resource { }, Schema: resourceNatGatewaySchema(), + + CustomizeDiff: pluginsdk.CustomizeDiffShim(func(ctx context.Context, d *pluginsdk.ResourceDiff, _ interface{}) error { + skuName := d.Get("sku_name").(string) + + if skuName == string(natgateways.NatGatewaySkuNameStandardVTwo) { + zonesRaw := d.Get("zones").(*schema.Set).List() + if len(zonesRaw) == 0 { + return fmt.Errorf("`zones` must be set to [1, 2, 3] when using StandardV2 SKU") + } + + zones := make([]string, 0, len(zonesRaw)) + for _, z := range zonesRaw { + zones = append(zones, z.(string)) + } + + sort.Strings(zones) + + requiredZones := []string{"1", "2", "3"} + if len(zones) != 3 || zones[0] != "1" || zones[1] != "2" || zones[2] != "3" { + return fmt.Errorf("`zones` must be set to [1, 2, 3] when using `StandardV2` SKU, got %v", requiredZones) + } + } + + return nil + }), } } diff --git a/website/docs/r/nat_gateway.html.markdown b/website/docs/r/nat_gateway.html.markdown index 893084c35afe..f28c0142a9ad 100644 --- a/website/docs/r/nat_gateway.html.markdown +++ b/website/docs/r/nat_gateway.html.markdown @@ -43,7 +43,7 @@ The following arguments are supported: * `sku_name` - (Optional) The SKU which should be used. Possible values are `Standard` and `StandardV2`. Defaults to `Standard`. --> **Note:** When `StandardV2` is enabled, service API will also enable all availability zones. See [MS learn documentation](https://learn.microsoft.com/en-us/azure/nat-gateway/nat-overview#standardv2-nat-gateway) for more info. The user has to explicitly set this property in the Terraform configuration or handle it using `ignore_changes`. +-> **Note:** `StandardV2` SKU requires `zones` to be set to `[1,2,3]`, see [MS learn documentation](https://learn.microsoft.com/en-us/azure/nat-gateway/nat-overview#standardv2-nat-gateway) for more info. * `tags` - (Optional) A mapping of tags to assign to the resource. From 9cd164ffd16f59b8f823c02a6ff73b35ca58574e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cneil-yechenwei=E2=80=9D?= <“yechenwei2007@hotmail.com”> Date: Mon, 8 Dec 2025 14:21:27 +0800 Subject: [PATCH 06/17] update tc --- .../services/network/nat_gateway_resource.go | 8 ++--- .../network/nat_gateway_resource_test.go | 33 +++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/internal/services/network/nat_gateway_resource.go b/internal/services/network/nat_gateway_resource.go index 424ed9a61c76..2205469d63de 100644 --- a/internal/services/network/nat_gateway_resource.go +++ b/internal/services/network/nat_gateway_resource.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "log" + "slices" "sort" "time" @@ -59,7 +60,7 @@ func resourceNatGateway() *pluginsdk.Resource { if skuName == string(natgateways.NatGatewaySkuNameStandardVTwo) { zonesRaw := d.Get("zones").(*schema.Set).List() if len(zonesRaw) == 0 { - return fmt.Errorf("`zones` must be set to [1, 2, 3] when using StandardV2 SKU") + return fmt.Errorf("`zones` must be set to [1, 2, 3] when using `StandardV2` SKU") } zones := make([]string, 0, len(zonesRaw)) @@ -69,9 +70,8 @@ func resourceNatGateway() *pluginsdk.Resource { sort.Strings(zones) - requiredZones := []string{"1", "2", "3"} - if len(zones) != 3 || zones[0] != "1" || zones[1] != "2" || zones[2] != "3" { - return fmt.Errorf("`zones` must be set to [1, 2, 3] when using `StandardV2` SKU, got %v", requiredZones) + if !slices.Equal(zones, []string{"1", "2", "3"}) { + return fmt.Errorf("`zones` must be set to [1, 2, 3] when using `StandardV2` SKU") } } diff --git a/internal/services/network/nat_gateway_resource_test.go b/internal/services/network/nat_gateway_resource_test.go index 22f11ef78a02..9daad921bef4 100644 --- a/internal/services/network/nat_gateway_resource_test.go +++ b/internal/services/network/nat_gateway_resource_test.go @@ -6,6 +6,7 @@ package network_test import ( "context" "fmt" + "regexp" "testing" "github.com/hashicorp/go-azure-helpers/lang/pointer" @@ -90,6 +91,18 @@ func TestAccNatGateway_standardVTwo(t *testing.T) { }) } +func TestAccNatGateway_standardVTwoWithoutZones(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_nat_gateway", "test") + r := NatGatewayResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.standardVTwoWithoutZones(data), + ExpectError: regexp.MustCompile(regexp.QuoteMeta("`zones` must be set to [1, 2, 3] when using `StandardV2` SKU")), + }, + }) +} + func (t NatGatewayResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := natgateways.ParseNatGatewayID(state.ID) if err != nil { @@ -244,3 +257,23 @@ resource "azurerm_nat_gateway" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } + +func (NatGatewayResource) standardVTwoWithoutZones(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-network-%d" + location = "%s" +} + +resource "azurerm_nat_gateway" "test" { + name = "acctestnatGateway-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku_name = "StandardV2" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} From 66824ac4d0d6fa755ff8ab5b8a20736354145b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cneil-yechenwei=E2=80=9D?= <“yechenwei2007@hotmail.com”> Date: Tue, 3 Feb 2026 09:37:12 +0800 Subject: [PATCH 07/17] add computed for skuv2 --- .../services/network/nat_gateway_resource.go | 38 +++++-------------- .../network/nat_gateway_resource_test.go | 33 ---------------- 2 files changed, 10 insertions(+), 61 deletions(-) diff --git a/internal/services/network/nat_gateway_resource.go b/internal/services/network/nat_gateway_resource.go index 279f9e74726f..7178dfd2057d 100644 --- a/internal/services/network/nat_gateway_resource.go +++ b/internal/services/network/nat_gateway_resource.go @@ -4,11 +4,8 @@ package network import ( - "context" "fmt" "log" - "slices" - "sort" "time" "github.com/hashicorp/go-azure-helpers/lang/pointer" @@ -53,30 +50,6 @@ func resourceNatGateway() *pluginsdk.Resource { }, Schema: resourceNatGatewaySchema(), - - CustomizeDiff: pluginsdk.CustomizeDiffShim(func(ctx context.Context, d *pluginsdk.ResourceDiff, _ interface{}) error { - skuName := d.Get("sku_name").(string) - - if skuName == string(natgateways.NatGatewaySkuNameStandardVTwo) { - zonesRaw := d.Get("zones").(*schema.Set).List() - if len(zonesRaw) == 0 { - return fmt.Errorf("`zones` must be set to [1, 2, 3] when using `StandardV2` SKU") - } - - zones := make([]string, 0, len(zonesRaw)) - for _, z := range zonesRaw { - zones = append(zones, z.(string)) - } - - sort.Strings(zones) - - if !slices.Equal(zones, []string{"1", "2", "3"}) { - return fmt.Errorf("`zones` must be set to [1, 2, 3] when using `StandardV2` SKU") - } - } - - return nil - }), } } @@ -107,7 +80,16 @@ func resourceNatGatewaySchema() map[string]*pluginsdk.Schema { ValidateFunc: validation.StringInSlice(natgateways.PossibleValuesForNatGatewaySkuName(), false), }, - "zones": commonschema.ZonesMultipleOptionalForceNew(), + "zones": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, "resource_guid": { Type: pluginsdk.TypeString, diff --git a/internal/services/network/nat_gateway_resource_test.go b/internal/services/network/nat_gateway_resource_test.go index 9daad921bef4..22f11ef78a02 100644 --- a/internal/services/network/nat_gateway_resource_test.go +++ b/internal/services/network/nat_gateway_resource_test.go @@ -6,7 +6,6 @@ package network_test import ( "context" "fmt" - "regexp" "testing" "github.com/hashicorp/go-azure-helpers/lang/pointer" @@ -91,18 +90,6 @@ func TestAccNatGateway_standardVTwo(t *testing.T) { }) } -func TestAccNatGateway_standardVTwoWithoutZones(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_nat_gateway", "test") - r := NatGatewayResource{} - - data.ResourceTest(t, r, []acceptance.TestStep{ - { - Config: r.standardVTwoWithoutZones(data), - ExpectError: regexp.MustCompile(regexp.QuoteMeta("`zones` must be set to [1, 2, 3] when using `StandardV2` SKU")), - }, - }) -} - func (t NatGatewayResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := natgateways.ParseNatGatewayID(state.ID) if err != nil { @@ -257,23 +244,3 @@ resource "azurerm_nat_gateway" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } - -func (NatGatewayResource) standardVTwoWithoutZones(data acceptance.TestData) string { - return fmt.Sprintf(` -provider "azurerm" { - features {} -} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-network-%d" - location = "%s" -} - -resource "azurerm_nat_gateway" "test" { - name = "acctestnatGateway-%d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - sku_name = "StandardV2" -} -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) -} From e3c8b251fbdd8a5cbb38697263639ad37f821e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cneil-yechenwei=E2=80=9D?= <“yechenwei2007@hotmail.com”> Date: Mon, 9 Feb 2026 09:02:33 +0800 Subject: [PATCH 08/17] update pr per comments --- internal/services/network/nat_gateway_resource.go | 1 + website/docs/r/public_ip.html.markdown | 4 ++-- website/docs/r/public_ip_prefix.html.markdown | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/services/network/nat_gateway_resource.go b/internal/services/network/nat_gateway_resource.go index 97a4d85ef886..18c9292a132d 100644 --- a/internal/services/network/nat_gateway_resource.go +++ b/internal/services/network/nat_gateway_resource.go @@ -83,6 +83,7 @@ func resourceNatGatewaySchema() map[string]*pluginsdk.Schema { "zones": { Type: schema.TypeSet, Optional: true, + // NOTE: O+C Azure may return availability zones from the API when not specified by the user Computed: true, ForceNew: true, Elem: &schema.Schema{ diff --git a/website/docs/r/public_ip.html.markdown b/website/docs/r/public_ip.html.markdown index 289c0043b5fe..3c79ea15bb27 100644 --- a/website/docs/r/public_ip.html.markdown +++ b/website/docs/r/public_ip.html.markdown @@ -78,11 +78,11 @@ The following arguments are supported: * `reverse_fqdn` - (Optional) A fully qualified domain name that resolves to this public IP address. If the reverseFqdn is specified, then a PTR DNS record is created pointing from the IP address in the in-addr.arpa domain to the reverse FQDN. -* `sku` - (Optional) The SKU of the Public IP. Possible values are `Basic`, `Standard` and `StandardV2`. Defaults to `Standard`. Changing this forces a new resource to be created. +* `sku` - (Optional) The SKU of the Public IP. Possible values are `Basic`, `Standard`, and `StandardV2`. Defaults to `Standard`. Changing this forces a new Public IP to be created. -> **Note:** Public IP Standard SKUs require `allocation_method` to be set to `Static`. --> **Note:** `Basic` will be deprecated. [Read more](https://azure.microsoft.com/en-us/updates/upgrade-to-standard-sku-public-ip-addresses-in-azure-by-30-september-2025-basic-sku-will-be-retired/). +!> **Note:** **On 30 September 2025, `Basic` SKU public IP addresses will be retired in Azure.** You can continue to use your existing `Basic` SKU public IP addresses until then, however, you will no longer be able to create new ones after 31 March 2025. Please see the Azure Update [retirement notification](https://azure.microsoft.com/updates/upgrade-to-standard-sku-public-ip-addresses-in-azure-by-30-september-2025-basic-sku-will-be-retired/) for more information. * `sku_tier` - (Optional) The SKU Tier that should be used for the Public IP. Possible values are `Regional` and `Global`. Defaults to `Regional`. Changing this forces a new resource to be created. diff --git a/website/docs/r/public_ip_prefix.html.markdown b/website/docs/r/public_ip_prefix.html.markdown index 06cb1b1f5e8a..f33423de90fa 100644 --- a/website/docs/r/public_ip_prefix.html.markdown +++ b/website/docs/r/public_ip_prefix.html.markdown @@ -47,7 +47,7 @@ The following arguments are supported: * `sku` - (Optional) The SKU of the Public IP Prefix. Possible values are `Standard` and `StandardV2`. Defaults to `Standard`. Changing this forces a new resource to be created. --> **Note:** Public IP Prefix can only be created with Standard SKUs at this time. +-> **Note:** Public IP Prefix can be created with the `Standard` and `StandardV2` SKUs. * `sku_tier` - (Optional) The SKU Tier that should be used for the Public IP. Possible values are `Regional` and `Global`. Defaults to `Regional`. Changing this forces a new resource to be created. From 574846bf68b137d2083e7a765352387c7f0a0e11 Mon Sep 17 00:00:00 2001 From: teowa <104055472+teowa@users.noreply.github.com> Date: Wed, 25 Feb 2026 06:50:46 +0000 Subject: [PATCH 09/17] add validation for zones --- internal/services/network/nat_gateway_resource.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/services/network/nat_gateway_resource.go b/internal/services/network/nat_gateway_resource.go index 75d9d5803e93..461f407cc01a 100644 --- a/internal/services/network/nat_gateway_resource.go +++ b/internal/services/network/nat_gateway_resource.go @@ -87,8 +87,12 @@ func resourceNatGatewaySchema() map[string]*pluginsdk.Schema { Computed: true, ForceNew: true, Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringIsNotEmpty, + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "1", + "2", + "3", + }, false), }, }, From d626e58f1e28ac7b828394a1632f92fa4de35621 Mon Sep 17 00:00:00 2001 From: teowa <104055472+teowa@users.noreply.github.com> Date: Wed, 4 Mar 2026 10:59:42 +0000 Subject: [PATCH 10/17] block creation of Basic SKU Public IP resources while allowing existing resources to be updated --- .../network/public_ip_data_source_test.go | 1 + .../services/network/public_ip_resource.go | 25 ++++++++++++++++ .../network/public_ip_resource_test.go | 30 +++++++++++++++---- .../network/public_ips_data_source_test.go | 7 +++-- website/docs/r/public_ip.html.markdown | 4 ++- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/internal/services/network/public_ip_data_source_test.go b/internal/services/network/public_ip_data_source_test.go index b3d5b8d1c724..bd8686206857 100644 --- a/internal/services/network/public_ip_data_source_test.go +++ b/internal/services/network/public_ip_data_source_test.go @@ -40,6 +40,7 @@ func TestAccDataSourcePublicIP_static(t *testing.T) { } func TestAccDataSourcePublicIP_dynamic(t *testing.T) { + skipIfBasicSkuPublicIPDeprecated(t) data := acceptance.BuildTestData(t, "data.azurerm_public_ip", "test") r := PublicIPDataSource{} diff --git a/internal/services/network/public_ip_resource.go b/internal/services/network/public_ip_resource.go index 092bcb290905..b067c40bad0b 100644 --- a/internal/services/network/public_ip_resource.go +++ b/internal/services/network/public_ip_resource.go @@ -179,6 +179,18 @@ func resourcePublicIp() *pluginsdk.Resource { }, CustomizeDiff: pluginsdk.CustomDiffWithAll( + pluginsdk.CustomizeDiffShim(func(_ context.Context, d *pluginsdk.ResourceDiff, _ interface{}) error { + if !isPublicIPBasicSkuDeprecatedForCreation() { + return nil + } + + sku := d.Get("sku").(string) + if strings.EqualFold(sku, string(publicipaddresses.PublicIPAddressSkuNameBasic)) && d.HasChanges("name", "resource_group_name", "location", "allocation_method", "edge_zone", "ip_version", "sku", "sku_tier", "public_ip_prefix_id", "ip_tags", "zones") { + return fmt.Errorf("%s", publicIPBasicSkuCreateDeprecationMessage) + } + + return nil + }), pluginsdk.ForceNewIfChange("domain_name_label_scope", func(ctx context.Context, old, new, meta interface{}) bool { return old.(string) != "" || new.(string) == "" }), @@ -186,6 +198,19 @@ func resourcePublicIp() *pluginsdk.Resource { } } +const publicIPBasicSkuCreateDeprecationMessage = "creation of new `Basic` SKU public IP addresses is no longer permitted following its deprecation on March 31, 2025. This also affects `allocation_method` set to `Dynamic`, as it is only available with the `Basic` SKU. Existing `Basic` SKU public IP addresses can continue to be used until September 30, 2025. For more information, see https://azure.microsoft.com/updates/upgrade-to-standard-sku-public-ip-addresses-in-azure-by-30-september-2025-basic-sku-will-be-retired/" + +func isPublicIPBasicSkuDeprecatedForCreation() bool { + losAngelesLocation, err := time.LoadLocation("America/Los_Angeles") + if err != nil { + losAngelesLocation = time.UTC + } + + deprecationTime := time.Date(2025, time.April, 1, 0, 0, 0, 0, losAngelesLocation) + now := time.Now() + return now.After(deprecationTime.In(now.Location())) +} + func resourcePublicIpCreate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.PublicIPAddresses subscriptionId := meta.(*clients.Client).Account.SubscriptionId diff --git a/internal/services/network/public_ip_resource_test.go b/internal/services/network/public_ip_resource_test.go index fb046664fd66..3d94478a5953 100644 --- a/internal/services/network/public_ip_resource_test.go +++ b/internal/services/network/public_ip_resource_test.go @@ -22,6 +22,7 @@ import ( type PublicIpResource struct{} func TestAccPublicIpStatic_basic(t *testing.T) { + skipIfBasicSkuPublicIPDeprecated(t) data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -41,6 +42,7 @@ func TestAccPublicIpStatic_basic(t *testing.T) { } func TestAccPublicIpStatic_requiresImport(t *testing.T) { + skipIfBasicSkuPublicIPDeprecated(t) data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -96,6 +98,7 @@ func TestAccPublicIp_zonesMultiple(t *testing.T) { } func TestAccPublicIpStatic_basic_withDNSLabel(t *testing.T) { + skipIfBasicSkuPublicIPDeprecated(t) data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} dnl := fmt.Sprintf("acctestdnl-%d", data.RandomInteger) @@ -131,6 +134,7 @@ func TestAccPublicIpStatic_standard_withIPv6(t *testing.T) { } func TestAccPublicIpDynamic_basic_withIPv6(t *testing.T) { + skipIfBasicSkuPublicIPDeprecated(t) data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} ipVersion := "IPv6" @@ -148,6 +152,7 @@ func TestAccPublicIpDynamic_basic_withIPv6(t *testing.T) { } func TestAccPublicIpStatic_basic_defaultsToIPv4(t *testing.T) { + skipIfBasicSkuPublicIPDeprecated(t) data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -164,6 +169,7 @@ func TestAccPublicIpStatic_basic_defaultsToIPv4(t *testing.T) { } func TestAccPublicIpStatic_basic_withIPv4(t *testing.T) { + skipIfBasicSkuPublicIPDeprecated(t) data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} ipVersion := "IPv4" @@ -341,6 +347,7 @@ func TestAccPublicIpStatic_standardPrefixWithTags(t *testing.T) { } func TestAccPublicIpDynamic_basic(t *testing.T) { + skipIfBasicSkuPublicIPDeprecated(t) data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -407,6 +414,7 @@ func TestAccPublicIpStatic_globalTier(t *testing.T) { } func TestAccPublicIpStatic_regionalTier(t *testing.T) { + skipIfBasicSkuPublicIPDeprecated(t) data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -754,7 +762,7 @@ resource "azurerm_public_ip" "test" { location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name allocation_method = "Static" - sku = "Basic" + sku = "Standard" domain_name_label = "acctest-%d" domain_name_label_scope = "TenantReuse" idle_timeout_in_minutes = 30 @@ -778,7 +786,7 @@ resource "azurerm_public_ip" "test" { location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name allocation_method = "Static" - sku = "Basic" + sku = "Standard" idle_timeout_in_minutes = %d } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, idleTimeout) @@ -844,7 +852,7 @@ resource "azurerm_public_ip" "test" { location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name allocation_method = "Static" - sku = "Basic" + sku = "Standard" tags = { environment = "Production" @@ -870,7 +878,7 @@ resource "azurerm_public_ip" "test" { location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name allocation_method = "Static" - sku = "Basic" + sku = "Standard" tags = { environment = "staging" @@ -896,7 +904,7 @@ resource "azurerm_public_ip" "test" { resource_group_name = azurerm_resource_group.test.name allocation_method = "Static" - sku = "Basic" + sku = "Standard" domain_name_label = "%s" } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, fmt.Sprintf("test%s", strings.ToLower(data.RandomStringOfLength(59)))) // prepend with test (4+59) to ensure the string starts with a letter @@ -1044,3 +1052,15 @@ resource "azurerm_public_ip" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } + +func skipIfBasicSkuPublicIPDeprecated(t *testing.T) { + losAngelesLocation, err := time.LoadLocation("America/Los_Angeles") + if err != nil { + losAngelesLocation = time.UTC + } + + deprecationTime := time.Date(2025, time.April, 1, 0, 0, 0, 0, losAngelesLocation) + if !time.Now().In(losAngelesLocation).Before(deprecationTime) { + t.Skip("skipping as Basic SKU public IP creation is no longer permitted after March 31, 2025") + } +} diff --git a/internal/services/network/public_ips_data_source_test.go b/internal/services/network/public_ips_data_source_test.go index e755236aad16..fad097f3caaa 100644 --- a/internal/services/network/public_ips_data_source_test.go +++ b/internal/services/network/public_ips_data_source_test.go @@ -56,6 +56,7 @@ func TestAccDataSourcePublicIPs_assigned(t *testing.T) { } func TestAccDataSourcePublicIPs_allocationType(t *testing.T) { + skipIfBasicSkuPublicIPDeprecated(t) data := acceptance.BuildTestData(t, "data.azurerm_public_ips", "test") r := PublicIPsResource{} @@ -164,7 +165,7 @@ resource "azurerm_public_ip" "test" { location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name allocation_method = "Static" - sku = "Basic" + sku = "Standard" idle_timeout_in_minutes = 30 tags = { @@ -178,7 +179,7 @@ resource "azurerm_public_ip" "test2" { location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name allocation_method = "Static" - sku = "Basic" + sku = "Standard" idle_timeout_in_minutes = 30 tags = { @@ -230,7 +231,7 @@ resource "azurerm_public_ip" "static" { location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name allocation_method = "Static" - sku = "Basic" + sku = "Standard" idle_timeout_in_minutes = 30 tags = { diff --git a/website/docs/r/public_ip.html.markdown b/website/docs/r/public_ip.html.markdown index 3c79ea15bb27..ee6d4d81fed7 100644 --- a/website/docs/r/public_ip.html.markdown +++ b/website/docs/r/public_ip.html.markdown @@ -46,6 +46,8 @@ The following arguments are supported: ~> **Note:** `Dynamic` Public IP Addresses aren't allocated until they're assigned to a resource (such as a Virtual Machine or a Load Balancer) by design within Azure. See `ip_address` argument. +!> **Note:** `Dynamic` allocation is only available with `Basic` SKU public IP addresses. Since `Basic` SKU public IP addresses have been deprecated (see `sku` below), `Dynamic` allocation is no longer available for new public IP addresses. + --- * `zones` - (Optional) A collection containing the availability zone to allocate the Public IP in. Changing this forces a new resource to be created. @@ -72,7 +74,7 @@ The following arguments are supported: * `ip_version` - (Optional) The IP Version to use, IPv6 or IPv4. Changing this forces a new resource to be created. Defaults to `IPv4`. --> **Note:** Only `static` IP address allocation is supported for IPv6. +-> **Note:** Only `Static` IP address allocation is supported for IPv6. * `public_ip_prefix_id` - (Optional) If specified then public IP address allocated will be provided from the public IP prefix resource. Changing this forces a new resource to be created. From 348e749ec5b5695c9dbc6f508d217c89718f8fd3 Mon Sep 17 00:00:00 2001 From: teowa <104055472+teowa@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:40:17 +0000 Subject: [PATCH 11/17] update doc; fix code --- .../network/nat_gateway_resource_test.go | 1 - .../network/public_ip_prefix_resource_test.go | 29 ++++- .../services/network/public_ip_resource.go | 3 +- .../network/public_ip_resource_test.go | 105 ++++++++++++------ website/docs/r/nat_gateway.html.markdown | 12 +- ...ateway_public_ip_association.html.markdown | 2 + ...public_ip_prefix_association.html.markdown | 2 + website/docs/r/public_ip.html.markdown | 2 +- website/docs/r/public_ip_prefix.html.markdown | 2 +- 9 files changed, 108 insertions(+), 50 deletions(-) diff --git a/internal/services/network/nat_gateway_resource_test.go b/internal/services/network/nat_gateway_resource_test.go index 0523aed4fe27..a53ff711d2b1 100644 --- a/internal/services/network/nat_gateway_resource_test.go +++ b/internal/services/network/nat_gateway_resource_test.go @@ -240,7 +240,6 @@ resource "azurerm_nat_gateway" "test" { location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name sku_name = "StandardV2" - zones = ["1", "2", "3"] } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } diff --git a/internal/services/network/public_ip_prefix_resource_test.go b/internal/services/network/public_ip_prefix_resource_test.go index a76ddc0b917e..a996aceb4930 100644 --- a/internal/services/network/public_ip_prefix_resource_test.go +++ b/internal/services/network/public_ip_prefix_resource_test.go @@ -69,9 +69,27 @@ func TestAccPublicIpPrefix_globalTier(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.sku_tier(data, string(publicipprefixes.PublicIPPrefixSkuTierGlobal)), + Config: r.sku_tier(data, string(publicipprefixes.PublicIPPrefixSkuNameStandard), string(publicipprefixes.PublicIPPrefixSkuTierGlobal)), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("sku").HasValue(string(publicipprefixes.PublicIPPrefixSkuNameStandard)), + check.That(data.ResourceName).Key("sku_tier").HasValue(string(publicipprefixes.PublicIPPrefixSkuTierGlobal)), + ), + }, + data.ImportStep(), + }) +} + +func TestAccPublicIpPrefix_standardVTwoGlobalTier(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_public_ip_prefix", "test") + r := PublicIpPrefixResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.sku_tier(data, string(publicipprefixes.PublicIPPrefixSkuNameStandardVTwo), string(publicipprefixes.PublicIPPrefixSkuTierGlobal)), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("sku").HasValue(string(publicipprefixes.PublicIPPrefixSkuNameStandardVTwo)), check.That(data.ResourceName).Key("sku_tier").HasValue(string(publicipprefixes.PublicIPPrefixSkuTierGlobal)), ), }, @@ -104,9 +122,10 @@ func TestAccPublicIpPrefix_regionalTier(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.sku_tier(data, string(publicipprefixes.PublicIPPrefixSkuTierRegional)), + Config: r.sku_tier(data, string(publicipprefixes.PublicIPPrefixSkuNameStandard), string(publicipprefixes.PublicIPPrefixSkuTierRegional)), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("sku").HasValue(string(publicipprefixes.PublicIPPrefixSkuNameStandard)), check.That(data.ResourceName).Key("sku_tier").HasValue(string(publicipprefixes.PublicIPPrefixSkuTierRegional)), ), }, @@ -393,7 +412,7 @@ resource "azurerm_public_ip_prefix" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } -func (PublicIpPrefixResource) sku_tier(data acceptance.TestData, tier string) string { +func (PublicIpPrefixResource) sku_tier(data acceptance.TestData, sku string, tier string) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -408,10 +427,10 @@ resource "azurerm_public_ip_prefix" "test" { resource_group_name = azurerm_resource_group.test.name name = "acctestpublicipprefix-%d" location = azurerm_resource_group.test.location + sku = "%s" sku_tier = "%s" - sku = "StandardV2" } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, tier) +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, sku, tier) } func (PublicIpPrefixResource) zonesSingle(data acceptance.TestData) string { diff --git a/internal/services/network/public_ip_resource.go b/internal/services/network/public_ip_resource.go index b067c40bad0b..fabb0a44406e 100644 --- a/internal/services/network/public_ip_resource.go +++ b/internal/services/network/public_ip_resource.go @@ -5,6 +5,7 @@ package network import ( "context" + "errors" "fmt" "log" "strings" @@ -186,7 +187,7 @@ func resourcePublicIp() *pluginsdk.Resource { sku := d.Get("sku").(string) if strings.EqualFold(sku, string(publicipaddresses.PublicIPAddressSkuNameBasic)) && d.HasChanges("name", "resource_group_name", "location", "allocation_method", "edge_zone", "ip_version", "sku", "sku_tier", "public_ip_prefix_id", "ip_tags", "zones") { - return fmt.Errorf("%s", publicIPBasicSkuCreateDeprecationMessage) + return errors.New(publicIPBasicSkuCreateDeprecationMessage) } return nil diff --git a/internal/services/network/public_ip_resource_test.go b/internal/services/network/public_ip_resource_test.go index 3d94478a5953..1ebb7593e359 100644 --- a/internal/services/network/public_ip_resource_test.go +++ b/internal/services/network/public_ip_resource_test.go @@ -227,6 +227,7 @@ func TestAccPublicIpStatic_standard_withDDoS(t *testing.T) { } func TestAccPublicIpStatic_disappears(t *testing.T) { + skipIfBasicSkuPublicIPDeprecated(t) data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -291,7 +292,7 @@ func TestAccPublicIpStatic_update(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.static_basic(data), + Config: r.static_standard(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -401,12 +402,30 @@ func TestAccPublicIpStatic_globalTier(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.globalTier(data), + Config: r.skuTier(data, string(publicipaddresses.PublicIPAddressSkuNameStandard), string(publicipaddresses.PublicIPAddressSkuTierGlobal)), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("ip_address").Exists(), - check.That(data.ResourceName).Key("sku").HasValue("Standard"), - check.That(data.ResourceName).Key("sku_tier").HasValue("Global"), + check.That(data.ResourceName).Key("sku").HasValue(string(publicipaddresses.PublicIPAddressSkuNameStandard)), + check.That(data.ResourceName).Key("sku_tier").HasValue(string(publicipaddresses.PublicIPAddressSkuTierGlobal)), + ), + }, + data.ImportStep(), + }) +} + +func TestAccPublicIpStatic_standardV2GlobalTier(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") + r := PublicIpResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.skuTier(data, string(publicipaddresses.PublicIPAddressSkuNameStandardVTwo), string(publicipaddresses.PublicIPAddressSkuTierGlobal)), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("ip_address").Exists(), + check.That(data.ResourceName).Key("sku").HasValue(string(publicipaddresses.PublicIPAddressSkuNameStandardVTwo)), + check.That(data.ResourceName).Key("sku_tier").HasValue(string(publicipaddresses.PublicIPAddressSkuTierGlobal)), ), }, data.ImportStep(), @@ -414,18 +433,35 @@ func TestAccPublicIpStatic_globalTier(t *testing.T) { } func TestAccPublicIpStatic_regionalTier(t *testing.T) { - skipIfBasicSkuPublicIPDeprecated(t) data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.regionalTier(data), + Config: r.skuTier(data, string(publicipaddresses.PublicIPAddressSkuNameStandard), string(publicipaddresses.PublicIPAddressSkuTierRegional)), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("ip_address").Exists(), - check.That(data.ResourceName).Key("sku").HasValue("Basic"), - check.That(data.ResourceName).Key("sku_tier").HasValue("Regional"), + check.That(data.ResourceName).Key("sku").HasValue(string(publicipaddresses.PublicIPAddressSkuNameStandard)), + check.That(data.ResourceName).Key("sku_tier").HasValue(string(publicipaddresses.PublicIPAddressSkuTierRegional)), + ), + }, + data.ImportStep(), + }) +} + +func TestAccPublicIpStatic_standardV2RegionalTier(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") + r := PublicIpResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.skuTier(data, string(publicipaddresses.PublicIPAddressSkuNameStandardVTwo), string(publicipaddresses.PublicIPAddressSkuTierRegional)), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("ip_address").Exists(), + check.That(data.ResourceName).Key("sku").HasValue(string(publicipaddresses.PublicIPAddressSkuNameStandardVTwo)), + check.That(data.ResourceName).Key("sku_tier").HasValue(string(publicipaddresses.PublicIPAddressSkuTierRegional)), ), }, data.ImportStep(), @@ -477,7 +513,28 @@ func (PublicIpResource) Destroy(ctx context.Context, client *clients.Client, sta } func (r PublicIpResource) basic(data acceptance.TestData) string { - return r.static_basic(data) + return r.static_standard(data) +} + +func (PublicIpResource) static_standard(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +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" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } func (PublicIpResource) static_basic(data acceptance.TestData) string { @@ -936,29 +993,7 @@ resource "azurerm_public_ip" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } -func (PublicIpResource) globalTier(data acceptance.TestData) string { - return fmt.Sprintf(` -provider "azurerm" { - features {} -} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" -} - -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" - sku_tier = "Global" -} -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) -} - -func (PublicIpResource) regionalTier(data acceptance.TestData) string { +func (PublicIpResource) skuTier(data acceptance.TestData, sku string, tier string) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -974,10 +1009,10 @@ resource "azurerm_public_ip" "test" { location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name allocation_method = "Static" - sku = "Basic" - sku_tier = "Regional" + sku = "%s" + sku_tier = "%s" } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, sku, tier) } func (PublicIpResource) zonesSingle(data acceptance.TestData) string { diff --git a/website/docs/r/nat_gateway.html.markdown b/website/docs/r/nat_gateway.html.markdown index f28c0142a9ad..5c2054f27e93 100644 --- a/website/docs/r/nat_gateway.html.markdown +++ b/website/docs/r/nat_gateway.html.markdown @@ -3,7 +3,7 @@ subcategory: "Network" layout: "azurerm" page_title: "Azure Resource Manager: azurerm_nat_gateway" description: |- - Manages a Azure NAT Gateway. + Manages an Azure NAT Gateway. --- # azurerm_nat_gateway @@ -43,13 +43,13 @@ The following arguments are supported: * `sku_name` - (Optional) The SKU which should be used. Possible values are `Standard` and `StandardV2`. Defaults to `Standard`. --> **Note:** `StandardV2` SKU requires `zones` to be set to `[1,2,3]`, see [MS learn documentation](https://learn.microsoft.com/en-us/azure/nat-gateway/nat-overview#standardv2-nat-gateway) for more info. +* `zones` - (Optional) A list of Availability Zones in which this NAT Gateway should be located. Changing this forces a new NAT Gateway to be created. -* `tags` - (Optional) A mapping of tags to assign to the resource. +-> **Note:** For `Standard`, `zones` may be omitted for a no-zone deployment or set to a single Availability Zone. For more information, please see the [Azure documentation](https://learn.microsoft.com/azure/nat-gateway/nat-overview#availability-zones). -* `zones` - (Optional) A list of Availability Zones in which this NAT Gateway should be located. Changing this forces a new NAT Gateway to be created. +~> **Note:** For `StandardV2`, `zones` should usually be omitted. `StandardV2` NAT Gateway is zone-redundant by default, and Azure may return `zones` as `1`, `2`, and `3` in state even when `zones` is not specified, see [MS learn documentation](https://learn.microsoft.com/azure/nat-gateway/nat-overview#standardv2-nat-gateway) for more info. --> **Note:** Only one Availability Zone can be defined. For more information, please check out the [Azure documentation](https://learn.microsoft.com/en-us/azure/nat-gateway/nat-overview#availability-zones) +* `tags` - (Optional) A mapping of tags to assign to the resource. ## Attributes Reference @@ -70,7 +70,7 @@ The `timeouts` block allows you to specify [timeouts](https://developer.hashicor ## Import -NAT Gateway can be imported using the `resource id`, e.g. +A NAT Gateway can be imported using the `resource id`, e.g. ```shell terraform import azurerm_nat_gateway.test /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/natGateways/gateway1 diff --git a/website/docs/r/nat_gateway_public_ip_association.html.markdown b/website/docs/r/nat_gateway_public_ip_association.html.markdown index dd4664fe11f2..8a3a59b333e4 100644 --- a/website/docs/r/nat_gateway_public_ip_association.html.markdown +++ b/website/docs/r/nat_gateway_public_ip_association.html.markdown @@ -48,6 +48,8 @@ The following arguments are supported: * `public_ip_address_id` - (Required) The ID of the Public IP which this NAT Gateway which should be connected to. Changing this forces a new resource to be created. +~> **Note:** When `nat_gateway_id` references a `StandardV2` NAT Gateway, `public_ip_address_id` must reference a `StandardV2` Public IP. Azure rejects `Standard` Public IPs with `StandardV2` NAT Gateways, and this incompatibility is not validated during terraform plan phase. + ## Attributes Reference In addition to the Arguments listed above - the following Attributes are exported: diff --git a/website/docs/r/nat_gateway_public_ip_prefix_association.html.markdown b/website/docs/r/nat_gateway_public_ip_prefix_association.html.markdown index e36f9ed95e33..1e2158244d3e 100644 --- a/website/docs/r/nat_gateway_public_ip_prefix_association.html.markdown +++ b/website/docs/r/nat_gateway_public_ip_prefix_association.html.markdown @@ -48,6 +48,8 @@ The following arguments are supported: * `public_ip_prefix_id` - (Required) The ID of the Public IP Prefix which this NAT Gateway which should be connected to. Changing this forces a new resource to be created. +~> **Note:** When `nat_gateway_id` references a `StandardV2` NAT Gateway, `public_ip_prefix_id` must reference a `StandardV2` Public IP Prefix. Azure rejects `Standard` Public IP Prefixes with `StandardV2` NAT Gateways, and this incompatibility is not validated during terraform plan phase. + ## Attributes Reference In addition to the Arguments listed above - the following Attributes are exported: diff --git a/website/docs/r/public_ip.html.markdown b/website/docs/r/public_ip.html.markdown index ee6d4d81fed7..470bedeb0a3d 100644 --- a/website/docs/r/public_ip.html.markdown +++ b/website/docs/r/public_ip.html.markdown @@ -88,7 +88,7 @@ The following arguments are supported: * `sku_tier` - (Optional) The SKU Tier that should be used for the Public IP. Possible values are `Regional` and `Global`. Defaults to `Regional`. Changing this forces a new resource to be created. --> **Note:** When `sku_tier` is set to `Global`, `sku` must be set to `Standard`. +-> **Note:** When `sku_tier` is set to `Global`, `sku` must be set to `Standard` or `StandardV2`. * `tags` - (Optional) A mapping of tags to assign to the resource. diff --git a/website/docs/r/public_ip_prefix.html.markdown b/website/docs/r/public_ip_prefix.html.markdown index f33423de90fa..d646bc80cf24 100644 --- a/website/docs/r/public_ip_prefix.html.markdown +++ b/website/docs/r/public_ip_prefix.html.markdown @@ -49,7 +49,7 @@ The following arguments are supported: -> **Note:** Public IP Prefix can be created with the `Standard` and `StandardV2` SKUs. -* `sku_tier` - (Optional) The SKU Tier that should be used for the Public IP. Possible values are `Regional` and `Global`. Defaults to `Regional`. Changing this forces a new resource to be created. +* `sku_tier` - (Optional) The SKU Tier that should be used for the Public IP Prefix. Possible values are `Regional` and `Global`. Defaults to `Regional`. Changing this forces a new resource to be created. * `ip_version` - (Optional) The IP Version to use, `IPv6` or `IPv4`. Changing this forces a new resource to be created. Default is `IPv4`. From 2de750c5dcae34dffcee9424482247c43af25c41 Mon Sep 17 00:00:00 2001 From: teowa <104055472+teowa@users.noreply.github.com> Date: Wed, 11 Mar 2026 05:25:45 +0000 Subject: [PATCH 12/17] fix doc --- website/docs/r/public_ip.html.markdown | 4 ++-- website/docs/r/public_ip_prefix.html.markdown | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/website/docs/r/public_ip.html.markdown b/website/docs/r/public_ip.html.markdown index 470bedeb0a3d..f8742c21c4d2 100644 --- a/website/docs/r/public_ip.html.markdown +++ b/website/docs/r/public_ip.html.markdown @@ -80,7 +80,7 @@ The following arguments are supported: * `reverse_fqdn` - (Optional) A fully qualified domain name that resolves to this public IP address. If the reverseFqdn is specified, then a PTR DNS record is created pointing from the IP address in the in-addr.arpa domain to the reverse FQDN. -* `sku` - (Optional) The SKU of the Public IP. Possible values are `Basic`, `Standard`, and `StandardV2`. Defaults to `Standard`. Changing this forces a new Public IP to be created. +* `sku` - (Optional) The SKU of the Public IP. Possible values are `Basic`, `Standard`, and `StandardV2`. Defaults to `Standard`. Changing this forces a new resource to be created. -> **Note:** Public IP Standard SKUs require `allocation_method` to be set to `Static`. @@ -88,7 +88,7 @@ The following arguments are supported: * `sku_tier` - (Optional) The SKU Tier that should be used for the Public IP. Possible values are `Regional` and `Global`. Defaults to `Regional`. Changing this forces a new resource to be created. --> **Note:** When `sku_tier` is set to `Global`, `sku` must be set to `Standard` or `StandardV2`. +-> **Note:** When `sku_tier` is set to `Global`, `sku` cannot be `Basic`. * `tags` - (Optional) A mapping of tags to assign to the resource. diff --git a/website/docs/r/public_ip_prefix.html.markdown b/website/docs/r/public_ip_prefix.html.markdown index d646bc80cf24..9f393b50874e 100644 --- a/website/docs/r/public_ip_prefix.html.markdown +++ b/website/docs/r/public_ip_prefix.html.markdown @@ -47,8 +47,6 @@ The following arguments are supported: * `sku` - (Optional) The SKU of the Public IP Prefix. Possible values are `Standard` and `StandardV2`. Defaults to `Standard`. Changing this forces a new resource to be created. --> **Note:** Public IP Prefix can be created with the `Standard` and `StandardV2` SKUs. - * `sku_tier` - (Optional) The SKU Tier that should be used for the Public IP Prefix. Possible values are `Regional` and `Global`. Defaults to `Regional`. Changing this forces a new resource to be created. * `ip_version` - (Optional) The IP Version to use, `IPv6` or `IPv4`. Changing this forces a new resource to be created. Default is `IPv4`. From 79d6261653884b9852f0ca454f6226ba4e957944 Mon Sep 17 00:00:00 2001 From: teowa <104055472+teowa@users.noreply.github.com> Date: Fri, 13 Mar 2026 01:25:32 +0000 Subject: [PATCH 13/17] update sku validation --- .../services/network/nat_gateway_resource.go | 9 +++------ .../network/public_ip_prefix_resource.go | 14 ++++++++++++++ .../network/public_ip_prefix_resource_test.go | 17 ----------------- .../services/network/public_ip_resource.go | 8 ++++++++ .../network/public_ip_resource_test.go | 18 ------------------ website/docs/r/nat_gateway.html.markdown | 2 +- website/docs/r/public_ip.html.markdown | 2 +- website/docs/r/public_ip_prefix.html.markdown | 2 ++ 8 files changed, 29 insertions(+), 43 deletions(-) diff --git a/internal/services/network/nat_gateway_resource.go b/internal/services/network/nat_gateway_resource.go index 461f407cc01a..7b03ec4e6f61 100644 --- a/internal/services/network/nat_gateway_resource.go +++ b/internal/services/network/nat_gateway_resource.go @@ -76,6 +76,7 @@ func resourceNatGatewaySchema() map[string]*pluginsdk.Schema { "sku_name": { Type: pluginsdk.TypeString, Optional: true, + ForceNew: true, Default: string(natgateways.NatGatewaySkuNameStandard), ValidateFunc: validation.StringInSlice(natgateways.PossibleValuesForNatGatewaySkuName(), false), }, @@ -87,12 +88,8 @@ func resourceNatGatewaySchema() map[string]*pluginsdk.Schema { Computed: true, ForceNew: true, Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice([]string{ - "1", - "2", - "3", - }, false), + Type: schema.TypeString, + ValidateFunc: validation.StringIsNotEmpty, }, }, diff --git a/internal/services/network/public_ip_prefix_resource.go b/internal/services/network/public_ip_prefix_resource.go index d9750a0a4b98..7a6dd8651dd6 100644 --- a/internal/services/network/public_ip_prefix_resource.go +++ b/internal/services/network/public_ip_prefix_resource.go @@ -4,8 +4,11 @@ package network import ( + "context" + "errors" "fmt" "log" + "strings" "time" "github.com/hashicorp/go-azure-helpers/lang/pointer" @@ -108,6 +111,17 @@ func resourcePublicIpPrefix() *pluginsdk.Resource { "tags": commonschema.Tags(), }, + + CustomizeDiff: pluginsdk.CustomDiffWithAll( + pluginsdk.CustomizeDiffShim(func(_ context.Context, d *pluginsdk.ResourceDiff, _ interface{}) error { + skuTier := d.Get("sku_tier").(string) + sku := d.Get("sku").(string) + if strings.EqualFold(skuTier, string(publicipprefixes.PublicIPPrefixSkuTierGlobal)) && !strings.EqualFold(sku, string(publicipprefixes.PublicIPPrefixSkuNameStandard)) { + return errors.New("`sku` must be set to `Standard` when `sku_tier` is set to `Global`") + } + return nil + }), + ), } } diff --git a/internal/services/network/public_ip_prefix_resource_test.go b/internal/services/network/public_ip_prefix_resource_test.go index a996aceb4930..ae14327c9a37 100644 --- a/internal/services/network/public_ip_prefix_resource_test.go +++ b/internal/services/network/public_ip_prefix_resource_test.go @@ -80,23 +80,6 @@ func TestAccPublicIpPrefix_globalTier(t *testing.T) { }) } -func TestAccPublicIpPrefix_standardVTwoGlobalTier(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_public_ip_prefix", "test") - r := PublicIpPrefixResource{} - - data.ResourceTest(t, r, []acceptance.TestStep{ - { - Config: r.sku_tier(data, string(publicipprefixes.PublicIPPrefixSkuNameStandardVTwo), string(publicipprefixes.PublicIPPrefixSkuTierGlobal)), - Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("sku").HasValue(string(publicipprefixes.PublicIPPrefixSkuNameStandardVTwo)), - check.That(data.ResourceName).Key("sku_tier").HasValue(string(publicipprefixes.PublicIPPrefixSkuTierGlobal)), - ), - }, - data.ImportStep(), - }) -} - func TestAccPublicIpPrefix_customIpPrefix(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip_prefix", "test") r := PublicIpPrefixResource{} diff --git a/internal/services/network/public_ip_resource.go b/internal/services/network/public_ip_resource.go index fabb0a44406e..75f28fb7fbb8 100644 --- a/internal/services/network/public_ip_resource.go +++ b/internal/services/network/public_ip_resource.go @@ -192,6 +192,14 @@ func resourcePublicIp() *pluginsdk.Resource { return nil }), + pluginsdk.CustomizeDiffShim(func(_ context.Context, d *pluginsdk.ResourceDiff, _ interface{}) error { + skuTier := d.Get("sku_tier").(string) + sku := d.Get("sku").(string) + if strings.EqualFold(skuTier, string(publicipaddresses.PublicIPAddressSkuTierGlobal)) && !strings.EqualFold(sku, string(publicipaddresses.PublicIPAddressSkuNameStandard)) { + return errors.New("`sku` must be set to `Standard` when `sku_tier` is set to `Global`") + } + return nil + }), pluginsdk.ForceNewIfChange("domain_name_label_scope", func(ctx context.Context, old, new, meta interface{}) bool { return old.(string) != "" || new.(string) == "" }), diff --git a/internal/services/network/public_ip_resource_test.go b/internal/services/network/public_ip_resource_test.go index 1ebb7593e359..23d4b0f9e65e 100644 --- a/internal/services/network/public_ip_resource_test.go +++ b/internal/services/network/public_ip_resource_test.go @@ -414,24 +414,6 @@ func TestAccPublicIpStatic_globalTier(t *testing.T) { }) } -func TestAccPublicIpStatic_standardV2GlobalTier(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") - r := PublicIpResource{} - - data.ResourceTest(t, r, []acceptance.TestStep{ - { - Config: r.skuTier(data, string(publicipaddresses.PublicIPAddressSkuNameStandardVTwo), string(publicipaddresses.PublicIPAddressSkuTierGlobal)), - Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("ip_address").Exists(), - check.That(data.ResourceName).Key("sku").HasValue(string(publicipaddresses.PublicIPAddressSkuNameStandardVTwo)), - check.That(data.ResourceName).Key("sku_tier").HasValue(string(publicipaddresses.PublicIPAddressSkuTierGlobal)), - ), - }, - data.ImportStep(), - }) -} - func TestAccPublicIpStatic_regionalTier(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} diff --git a/website/docs/r/nat_gateway.html.markdown b/website/docs/r/nat_gateway.html.markdown index 5c2054f27e93..1faf8d6a9e01 100644 --- a/website/docs/r/nat_gateway.html.markdown +++ b/website/docs/r/nat_gateway.html.markdown @@ -41,7 +41,7 @@ The following arguments are supported: * `idle_timeout_in_minutes` - (Optional) The idle timeout which should be used in minutes. Defaults to `4`. -* `sku_name` - (Optional) The SKU which should be used. Possible values are `Standard` and `StandardV2`. Defaults to `Standard`. +* `sku_name` - (Optional) The SKU which should be used. Possible values are `Standard` and `StandardV2`. Defaults to `Standard`. Changing this forces a new resource to be created. * `zones` - (Optional) A list of Availability Zones in which this NAT Gateway should be located. Changing this forces a new NAT Gateway to be created. diff --git a/website/docs/r/public_ip.html.markdown b/website/docs/r/public_ip.html.markdown index f8742c21c4d2..e44ddb96fd9e 100644 --- a/website/docs/r/public_ip.html.markdown +++ b/website/docs/r/public_ip.html.markdown @@ -88,7 +88,7 @@ The following arguments are supported: * `sku_tier` - (Optional) The SKU Tier that should be used for the Public IP. Possible values are `Regional` and `Global`. Defaults to `Regional`. Changing this forces a new resource to be created. --> **Note:** When `sku_tier` is set to `Global`, `sku` cannot be `Basic`. +-> **Note:** When `sku_tier` is set to `Global`, `sku` must be set to `Standard`. * `tags` - (Optional) A mapping of tags to assign to the resource. diff --git a/website/docs/r/public_ip_prefix.html.markdown b/website/docs/r/public_ip_prefix.html.markdown index 9f393b50874e..9d3b01a86f5c 100644 --- a/website/docs/r/public_ip_prefix.html.markdown +++ b/website/docs/r/public_ip_prefix.html.markdown @@ -49,6 +49,8 @@ The following arguments are supported: * `sku_tier` - (Optional) The SKU Tier that should be used for the Public IP Prefix. Possible values are `Regional` and `Global`. Defaults to `Regional`. Changing this forces a new resource to be created. +-> **Note:** When `sku_tier` is set to `Global`, `sku` must be set to `Standard`. + * `ip_version` - (Optional) The IP Version to use, `IPv6` or `IPv4`. Changing this forces a new resource to be created. Default is `IPv4`. * `prefix_length` - (Optional) Specifies the number of bits of the prefix. The value can be set between 0 (4,294,967,296 addresses) and 31 (2 addresses). Defaults to `28`(16 addresses). Changing this forces a new resource to be created. From d53603ef3ac1333fae5f943be3cba833801a8a7a Mon Sep 17 00:00:00 2001 From: teowa <104055472+teowa@users.noreply.github.com> Date: Fri, 13 Mar 2026 01:32:36 +0000 Subject: [PATCH 14/17] update allocation validation for public IP --- internal/services/network/public_ip_resource.go | 6 +++--- website/docs/r/public_ip.html.markdown | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/services/network/public_ip_resource.go b/internal/services/network/public_ip_resource.go index 75f28fb7fbb8..1c0c31840a79 100644 --- a/internal/services/network/public_ip_resource.go +++ b/internal/services/network/public_ip_resource.go @@ -244,9 +244,9 @@ func resourcePublicIpCreate(d *pluginsdk.ResourceData, meta interface{}) error { sku := d.Get("sku").(string) ipAllocationMethod := d.Get("allocation_method").(string) - if strings.EqualFold(sku, "standard") { - if !strings.EqualFold(ipAllocationMethod, "static") { - return fmt.Errorf("static IP allocation must be used when creating Standard SKU public IP addresses") + if strings.EqualFold(sku, string(publicipaddresses.PublicIPAddressSkuNameStandard)) || strings.EqualFold(sku, string(publicipaddresses.PublicIPAddressSkuNameStandardVTwo)) { + if !strings.EqualFold(ipAllocationMethod, string(publicipaddresses.IPAllocationMethodStatic)) { + return fmt.Errorf("`allocation_method` must be set to `Static` when `sku` is set to `Standard` or `StandardV2`") } } diff --git a/website/docs/r/public_ip.html.markdown b/website/docs/r/public_ip.html.markdown index e44ddb96fd9e..4bd1d0415b12 100644 --- a/website/docs/r/public_ip.html.markdown +++ b/website/docs/r/public_ip.html.markdown @@ -82,7 +82,7 @@ The following arguments are supported: * `sku` - (Optional) The SKU of the Public IP. Possible values are `Basic`, `Standard`, and `StandardV2`. Defaults to `Standard`. Changing this forces a new resource to be created. --> **Note:** Public IP Standard SKUs require `allocation_method` to be set to `Static`. +-> **Note:** Public IP `Standard` and `StandardV2` SKUs require `allocation_method` to be set to `Static`. !> **Note:** **On 30 September 2025, `Basic` SKU public IP addresses will be retired in Azure.** You can continue to use your existing `Basic` SKU public IP addresses until then, however, you will no longer be able to create new ones after 31 March 2025. Please see the Azure Update [retirement notification](https://azure.microsoft.com/updates/upgrade-to-standard-sku-public-ip-addresses-in-azure-by-30-september-2025-basic-sku-will-be-retired/) for more information. From 1c0a85ccd9d0e6623c2ce3b17967f29c1ea0d3a4 Mon Sep 17 00:00:00 2001 From: teowa <104055472+teowa@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:59:10 +0000 Subject: [PATCH 15/17] fix basic sku public ip message --- internal/services/network/public_ip_resource.go | 6 +----- website/docs/r/nat_gateway.html.markdown | 2 +- website/docs/r/public_ip.html.markdown | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/internal/services/network/public_ip_resource.go b/internal/services/network/public_ip_resource.go index 1c0c31840a79..c7707b081436 100644 --- a/internal/services/network/public_ip_resource.go +++ b/internal/services/network/public_ip_resource.go @@ -181,10 +181,6 @@ func resourcePublicIp() *pluginsdk.Resource { CustomizeDiff: pluginsdk.CustomDiffWithAll( pluginsdk.CustomizeDiffShim(func(_ context.Context, d *pluginsdk.ResourceDiff, _ interface{}) error { - if !isPublicIPBasicSkuDeprecatedForCreation() { - return nil - } - sku := d.Get("sku").(string) if strings.EqualFold(sku, string(publicipaddresses.PublicIPAddressSkuNameBasic)) && d.HasChanges("name", "resource_group_name", "location", "allocation_method", "edge_zone", "ip_version", "sku", "sku_tier", "public_ip_prefix_id", "ip_tags", "zones") { return errors.New(publicIPBasicSkuCreateDeprecationMessage) @@ -207,7 +203,7 @@ func resourcePublicIp() *pluginsdk.Resource { } } -const publicIPBasicSkuCreateDeprecationMessage = "creation of new `Basic` SKU public IP addresses is no longer permitted following its deprecation on March 31, 2025. This also affects `allocation_method` set to `Dynamic`, as it is only available with the `Basic` SKU. Existing `Basic` SKU public IP addresses can continue to be used until September 30, 2025. For more information, see https://azure.microsoft.com/updates/upgrade-to-standard-sku-public-ip-addresses-in-azure-by-30-september-2025-basic-sku-will-be-retired/" +const publicIPBasicSkuCreateDeprecationMessage = "creation of new `Basic` SKU public IP addresses is no longer permitted following its deprecation on March 31, 2025. This also affects `allocation_method` set to `Dynamic`, as it is only available with the `Basic` SKU. For more information, see https://azure.microsoft.com/updates/upgrade-to-standard-sku-public-ip-addresses-in-azure-by-30-september-2025-basic-sku-will-be-retired/" func isPublicIPBasicSkuDeprecatedForCreation() bool { losAngelesLocation, err := time.LoadLocation("America/Los_Angeles") diff --git a/website/docs/r/nat_gateway.html.markdown b/website/docs/r/nat_gateway.html.markdown index 1faf8d6a9e01..8f6e35925907 100644 --- a/website/docs/r/nat_gateway.html.markdown +++ b/website/docs/r/nat_gateway.html.markdown @@ -47,7 +47,7 @@ The following arguments are supported: -> **Note:** For `Standard`, `zones` may be omitted for a no-zone deployment or set to a single Availability Zone. For more information, please see the [Azure documentation](https://learn.microsoft.com/azure/nat-gateway/nat-overview#availability-zones). -~> **Note:** For `StandardV2`, `zones` should usually be omitted. `StandardV2` NAT Gateway is zone-redundant by default, and Azure may return `zones` as `1`, `2`, and `3` in state even when `zones` is not specified, see [MS learn documentation](https://learn.microsoft.com/azure/nat-gateway/nat-overview#standardv2-nat-gateway) for more info. +~> **Note:** For `StandardV2`, it is recommend to omit `zones`. The `StandardV2` NAT Gateway is zone-redundant by default, and Azure returns all available `zones` which may differ from the zones specified in config, see [MS learn documentation](https://learn.microsoft.com/azure/nat-gateway/nat-overview#standardv2-nat-gateway) for more info. * `tags` - (Optional) A mapping of tags to assign to the resource. diff --git a/website/docs/r/public_ip.html.markdown b/website/docs/r/public_ip.html.markdown index 4bd1d0415b12..0846f0af1827 100644 --- a/website/docs/r/public_ip.html.markdown +++ b/website/docs/r/public_ip.html.markdown @@ -84,7 +84,7 @@ The following arguments are supported: -> **Note:** Public IP `Standard` and `StandardV2` SKUs require `allocation_method` to be set to `Static`. -!> **Note:** **On 30 September 2025, `Basic` SKU public IP addresses will be retired in Azure.** You can continue to use your existing `Basic` SKU public IP addresses until then, however, you will no longer be able to create new ones after 31 March 2025. Please see the Azure Update [retirement notification](https://azure.microsoft.com/updates/upgrade-to-standard-sku-public-ip-addresses-in-azure-by-30-september-2025-basic-sku-will-be-retired/) for more information. +!> **Note:** `sku` can no longer be set to `Basic` as of 31 March 2025 for new resources. This also affects `allocation_method` set to `Dynamic`, as it is only available with the `Basic` SKU. Please see the Azure Update [retirement notification](https://azure.microsoft.com/updates/upgrade-to-standard-sku-public-ip-addresses-in-azure-by-30-september-2025-basic-sku-will-be-retired/) for more information. * `sku_tier` - (Optional) The SKU Tier that should be used for the Public IP. Possible values are `Regional` and `Global`. Defaults to `Regional`. Changing this forces a new resource to be created. From aa8b5d5d085fac3332dc472db3013f9ecd5f71ae Mon Sep 17 00:00:00 2001 From: teowa <104055472+teowa@users.noreply.github.com> Date: Mon, 16 Mar 2026 01:17:45 +0000 Subject: [PATCH 16/17] fix lint --- internal/services/network/public_ip_resource.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/internal/services/network/public_ip_resource.go b/internal/services/network/public_ip_resource.go index c7707b081436..b400dd92a47d 100644 --- a/internal/services/network/public_ip_resource.go +++ b/internal/services/network/public_ip_resource.go @@ -205,17 +205,6 @@ func resourcePublicIp() *pluginsdk.Resource { const publicIPBasicSkuCreateDeprecationMessage = "creation of new `Basic` SKU public IP addresses is no longer permitted following its deprecation on March 31, 2025. This also affects `allocation_method` set to `Dynamic`, as it is only available with the `Basic` SKU. For more information, see https://azure.microsoft.com/updates/upgrade-to-standard-sku-public-ip-addresses-in-azure-by-30-september-2025-basic-sku-will-be-retired/" -func isPublicIPBasicSkuDeprecatedForCreation() bool { - losAngelesLocation, err := time.LoadLocation("America/Los_Angeles") - if err != nil { - losAngelesLocation = time.UTC - } - - deprecationTime := time.Date(2025, time.April, 1, 0, 0, 0, 0, losAngelesLocation) - now := time.Now() - return now.After(deprecationTime.In(now.Location())) -} - func resourcePublicIpCreate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.PublicIPAddresses subscriptionId := meta.(*clients.Client).Account.SubscriptionId From a1c8270b2fc4253782b59203fef83a988b5db052 Mon Sep 17 00:00:00 2001 From: teowa <104055472+teowa@users.noreply.github.com> Date: Mon, 23 Mar 2026 00:27:40 +0000 Subject: [PATCH 17/17] disallow zones for StandardV2; update test --- .../services/network/nat_gateway_resource.go | 23 +- .../network/public_ip_data_source_test.go | 13 +- .../network/public_ip_resource_test.go | 207 +++--------------- .../network/public_ips_data_source_test.go | 37 +--- website/docs/r/nat_gateway.html.markdown | 4 +- 5 files changed, 65 insertions(+), 219 deletions(-) diff --git a/internal/services/network/nat_gateway_resource.go b/internal/services/network/nat_gateway_resource.go index 7b03ec4e6f61..dc61cf8a3914 100644 --- a/internal/services/network/nat_gateway_resource.go +++ b/internal/services/network/nat_gateway_resource.go @@ -4,6 +4,7 @@ package network import ( + "context" "fmt" "log" "time" @@ -49,6 +50,16 @@ func resourceNatGateway() *pluginsdk.Resource { SchemaFunc: pluginsdk.GenerateIdentitySchema(&natgateways.NatGatewayId{}), }, + CustomizeDiff: func(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { + if diff.Get("sku_name").(string) == string(natgateways.NatGatewaySkuNameStandardVTwo) { + if !diff.GetRawConfig().AsValueMap()["zones"].IsNull() { + return fmt.Errorf("%s resources with `sku_name` set to `%s` are zone-redundant by default, Azure automatically deploys across all available zones. The `zones` argument must be omitted", natGatewayResourceName, natgateways.NatGatewaySkuNameStandardVTwo) + } + } + + return nil + }, + Schema: resourceNatGatewaySchema(), } } @@ -82,11 +93,13 @@ func resourceNatGatewaySchema() map[string]*pluginsdk.Schema { }, "zones": { - Type: schema.TypeSet, - Optional: true, - // NOTE: O+C Azure may return availability zones from the API when not specified by the user - Computed: true, - ForceNew: true, + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + DiffSuppressOnRefresh: true, + DiffSuppressFunc: func(_, _, _ string, d *schema.ResourceData) bool { + return d.Get("sku_name").(string) == string(natgateways.NatGatewaySkuNameStandardVTwo) + }, Elem: &schema.Schema{ Type: schema.TypeString, ValidateFunc: validation.StringIsNotEmpty, diff --git a/internal/services/network/public_ip_data_source_test.go b/internal/services/network/public_ip_data_source_test.go index bd8686206857..374efdf6b81f 100644 --- a/internal/services/network/public_ip_data_source_test.go +++ b/internal/services/network/public_ip_data_source_test.go @@ -39,8 +39,7 @@ func TestAccDataSourcePublicIP_static(t *testing.T) { }) } -func TestAccDataSourcePublicIP_dynamic(t *testing.T) { - skipIfBasicSkuPublicIPDeprecated(t) +func TestAccDataSourcePublicIP_staticMinimal(t *testing.T) { data := acceptance.BuildTestData(t, "data.azurerm_public_ip", "test") r := PublicIPDataSource{} @@ -49,13 +48,13 @@ func TestAccDataSourcePublicIP_dynamic(t *testing.T) { data.DataSourceTest(t, []acceptance.TestStep{ { - Config: r.dynamic(data, "IPv4"), + Config: r.staticMinimal(data, "IPv4"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).Key("name").HasValue(name), check.That(data.ResourceName).Key("resource_group_name").HasValue(resourceGroupName), check.That(data.ResourceName).Key("domain_name_label").HasValue(""), check.That(data.ResourceName).Key("fqdn").HasValue(""), - check.That(data.ResourceName).Key("ip_address").HasValue(""), + check.That(data.ResourceName).Key("ip_address").Exists(), check.That(data.ResourceName).Key("ip_version").HasValue("IPv4"), check.That(data.ResourceName).Key("tags.%").HasValue("1"), check.That(data.ResourceName).Key("tags.environment").HasValue("test"), @@ -101,7 +100,7 @@ data "azurerm_public_ip" "test" { `, resourceGroupName, data.Locations.Primary, name, data.RandomInteger) } -func (PublicIPDataSource) dynamic(data acceptance.TestData, ipVersion string) string { +func (PublicIPDataSource) staticMinimal(data acceptance.TestData, ipVersion string) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -116,8 +115,8 @@ resource "azurerm_public_ip" "test" { name = "acctestpublicip-%d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name - allocation_method = "Dynamic" - sku = "Basic" + allocation_method = "Static" + sku = "Standard" ip_version = "%s" diff --git a/internal/services/network/public_ip_resource_test.go b/internal/services/network/public_ip_resource_test.go index 23d4b0f9e65e..f93f569135b0 100644 --- a/internal/services/network/public_ip_resource_test.go +++ b/internal/services/network/public_ip_resource_test.go @@ -21,14 +21,13 @@ import ( type PublicIpResource struct{} -func TestAccPublicIpStatic_basic(t *testing.T) { - skipIfBasicSkuPublicIPDeprecated(t) +func TestAccPublicIp_basic(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.static_basic(data), + Config: r.basic(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("ip_address").Exists(), @@ -41,14 +40,13 @@ func TestAccPublicIpStatic_basic(t *testing.T) { }) } -func TestAccPublicIpStatic_requiresImport(t *testing.T) { - skipIfBasicSkuPublicIPDeprecated(t) +func TestAccPublicIp_requiresImport(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.static_basic(data), + Config: r.basic(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("ip_address").Exists(), @@ -97,15 +95,14 @@ func TestAccPublicIp_zonesMultiple(t *testing.T) { }) } -func TestAccPublicIpStatic_basic_withDNSLabel(t *testing.T) { - skipIfBasicSkuPublicIPDeprecated(t) +func TestAccPublicIp_standard_withDNSLabel(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} dnl := fmt.Sprintf("acctestdnl-%d", data.RandomInteger) data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.basic_withDNSLabel(data, dnl), + Config: r.standard_withDNSLabel(data, dnl), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("ip_address").Exists(), @@ -117,7 +114,7 @@ func TestAccPublicIpStatic_basic_withDNSLabel(t *testing.T) { }) } -func TestAccPublicIpStatic_standard_withIPv6(t *testing.T) { +func TestAccPublicIp_standard_withIPv6(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -133,32 +130,13 @@ func TestAccPublicIpStatic_standard_withIPv6(t *testing.T) { }) } -func TestAccPublicIpDynamic_basic_withIPv6(t *testing.T) { - skipIfBasicSkuPublicIPDeprecated(t) +func TestAccPublicIp_standard_defaultsToIPv4(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} - ipVersion := "IPv6" data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.dynamic_basic_withIPVersion(data, ipVersion), - Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("ip_version").HasValue("IPv6"), - ), - }, - data.ImportStep(), - }) -} - -func TestAccPublicIpStatic_basic_defaultsToIPv4(t *testing.T) { - skipIfBasicSkuPublicIPDeprecated(t) - data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") - r := PublicIpResource{} - - data.ResourceTest(t, r, []acceptance.TestStep{ - { - Config: r.static_basic(data), + Config: r.basic(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("ip_version").HasValue("IPv4"), @@ -168,15 +146,14 @@ func TestAccPublicIpStatic_basic_defaultsToIPv4(t *testing.T) { }) } -func TestAccPublicIpStatic_basic_withIPv4(t *testing.T) { - skipIfBasicSkuPublicIPDeprecated(t) +func TestAccPublicIp_standard_withIPv4(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} ipVersion := "IPv4" data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.static_basic_withIPVersion(data, ipVersion), + Config: r.standard_withIPVersion(data, ipVersion), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("ip_version").HasValue("IPv4"), @@ -186,7 +163,7 @@ func TestAccPublicIpStatic_basic_withIPv4(t *testing.T) { }) } -func TestAccPublicIpStatic_standardVTwo(t *testing.T) { +func TestAccPublicIp_standardVTwo(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -201,7 +178,7 @@ func TestAccPublicIpStatic_standardVTwo(t *testing.T) { }) } -func TestAccPublicIpStatic_standard_withDDoS(t *testing.T) { +func TestAccPublicIp_standard_withDDoS(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -226,20 +203,19 @@ func TestAccPublicIpStatic_standard_withDDoS(t *testing.T) { }) } -func TestAccPublicIpStatic_disappears(t *testing.T) { - skipIfBasicSkuPublicIPDeprecated(t) +func TestAccPublicIp_disappears(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} data.ResourceTest(t, r, []acceptance.TestStep{ data.DisappearsStep(acceptance.DisappearsStepData{ - Config: r.static_basic, + Config: r.basic, TestResource: r, }), }) } -func TestAccPublicIpStatic_idleTimeout(t *testing.T) { +func TestAccPublicIp_idleTimeout(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -261,7 +237,7 @@ func TestAccPublicIpStatic_idleTimeout(t *testing.T) { }) } -func TestAccPublicIpStatic_withTags(t *testing.T) { +func TestAccPublicIp_withTags(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -286,13 +262,13 @@ func TestAccPublicIpStatic_withTags(t *testing.T) { }) } -func TestAccPublicIpStatic_update(t *testing.T) { +func TestAccPublicIp_update(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.static_standard(data), + Config: r.basic(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -308,7 +284,7 @@ func TestAccPublicIpStatic_update(t *testing.T) { }) } -func TestAccPublicIpStatic_standardPrefix(t *testing.T) { +func TestAccPublicIp_standardPrefix(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -322,7 +298,7 @@ func TestAccPublicIpStatic_standardPrefix(t *testing.T) { }) } -func TestAccPublicIpStatic_standardPrefixWithTags(t *testing.T) { +func TestAccPublicIp_standardPrefixWithTags(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -347,23 +323,7 @@ func TestAccPublicIpStatic_standardPrefixWithTags(t *testing.T) { }) } -func TestAccPublicIpDynamic_basic(t *testing.T) { - skipIfBasicSkuPublicIPDeprecated(t) - data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") - r := PublicIpResource{} - - data.ResourceTest(t, r, []acceptance.TestStep{ - { - Config: r.dynamic_basic(data), - Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - data.ImportStep(), - }) -} - -func TestAccPublicIpStatic_canLabelBe63(t *testing.T) { +func TestAccPublicIp_canLabelBe63(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -380,7 +340,7 @@ func TestAccPublicIpStatic_canLabelBe63(t *testing.T) { }) } -func TestAccPublicIpStatic_ipTags(t *testing.T) { +func TestAccPublicIp_ipTags(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -396,7 +356,7 @@ func TestAccPublicIpStatic_ipTags(t *testing.T) { }) } -func TestAccPublicIpStatic_globalTier(t *testing.T) { +func TestAccPublicIp_globalTier(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -414,7 +374,7 @@ func TestAccPublicIpStatic_globalTier(t *testing.T) { }) } -func TestAccPublicIpStatic_regionalTier(t *testing.T) { +func TestAccPublicIp_regionalTier(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -432,7 +392,7 @@ func TestAccPublicIpStatic_regionalTier(t *testing.T) { }) } -func TestAccPublicIpStatic_standardV2RegionalTier(t *testing.T) { +func TestAccPublicIp_standardV2RegionalTier(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -450,7 +410,7 @@ func TestAccPublicIpStatic_standardV2RegionalTier(t *testing.T) { }) } -func TestAccPublicIpStatic_edgeZone(t *testing.T) { +func TestAccPublicIp_edgeZone(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_public_ip", "test") r := PublicIpResource{} @@ -494,11 +454,7 @@ func (PublicIpResource) Destroy(ctx context.Context, client *clients.Client, sta return pointer.To(true), nil } -func (r PublicIpResource) basic(data acceptance.TestData) string { - return r.static_standard(data) -} - -func (PublicIpResource) static_standard(data acceptance.TestData) string { +func (PublicIpResource) basic(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -519,27 +475,6 @@ resource "azurerm_public_ip" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } -func (PublicIpResource) static_basic(data acceptance.TestData) string { - return fmt.Sprintf(` -provider "azurerm" { - features {} -} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" -} - -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 = "Basic" -} -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) -} - func (r PublicIpResource) requiresImport(data acceptance.TestData) string { return fmt.Sprintf(` %s @@ -549,12 +484,12 @@ resource "azurerm_public_ip" "import" { location = azurerm_public_ip.test.location resource_group_name = azurerm_public_ip.test.resource_group_name allocation_method = azurerm_public_ip.test.allocation_method - sku = "Basic" + sku = azurerm_public_ip.test.sku } -`, r.static_basic(data)) +`, r.basic(data)) } -func (PublicIpResource) basic_withDNSLabel(data acceptance.TestData, dnsNameLabel string) string { +func (PublicIpResource) standard_withDNSLabel(data acceptance.TestData, dnsNameLabel string) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -570,34 +505,12 @@ resource "azurerm_public_ip" "test" { location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name allocation_method = "Static" - sku = "Basic" + sku = "Standard" domain_name_label = "%s" } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, dnsNameLabel) } -func (PublicIpResource) static_basic_withIPVersion(data acceptance.TestData, ipVersion string) string { - return fmt.Sprintf(` -provider "azurerm" { - features {} -} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" -} - -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 = "Basic" - ip_version = "%s" -} -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, ipVersion) -} - func (PublicIpResource) standardVTwo(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { @@ -831,50 +744,6 @@ resource "azurerm_public_ip" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, idleTimeout) } -func (PublicIpResource) dynamic_basic(data acceptance.TestData) string { - return fmt.Sprintf(` -provider "azurerm" { - features {} -} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" -} - -resource "azurerm_public_ip" "test" { - name = "acctestpublicip-%d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - allocation_method = "Dynamic" - sku = "Basic" -} -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) -} - -func (PublicIpResource) dynamic_basic_withIPVersion(data acceptance.TestData, ipVersion string) string { - return fmt.Sprintf(` -provider "azurerm" { - features {} -} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" -} - -resource "azurerm_public_ip" "test" { - name = "acctestpublicip-%d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - allocation_method = "Dynamic" - sku = "Basic" - - ip_version = "%s" -} -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, ipVersion) -} - func (PublicIpResource) withTags(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { @@ -1069,15 +938,3 @@ resource "azurerm_public_ip" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } - -func skipIfBasicSkuPublicIPDeprecated(t *testing.T) { - losAngelesLocation, err := time.LoadLocation("America/Los_Angeles") - if err != nil { - losAngelesLocation = time.UTC - } - - deprecationTime := time.Date(2025, time.April, 1, 0, 0, 0, 0, losAngelesLocation) - if !time.Now().In(losAngelesLocation).Before(deprecationTime) { - t.Skip("skipping as Basic SKU public IP creation is no longer permitted after March 31, 2025") - } -} diff --git a/internal/services/network/public_ips_data_source_test.go b/internal/services/network/public_ips_data_source_test.go index fad097f3caaa..4c6c4737d145 100644 --- a/internal/services/network/public_ips_data_source_test.go +++ b/internal/services/network/public_ips_data_source_test.go @@ -55,25 +55,21 @@ func TestAccDataSourcePublicIPs_assigned(t *testing.T) { }) } -func TestAccDataSourcePublicIPs_allocationType(t *testing.T) { - skipIfBasicSkuPublicIPDeprecated(t) +func TestAccDataSourcePublicIPs_staticAllocationType(t *testing.T) { data := acceptance.BuildTestData(t, "data.azurerm_public_ips", "test") r := PublicIPsResource{} staticDataSourceName := "data.azurerm_public_ips.static" - dynamicDataSourceName := "data.azurerm_public_ips.dynamic" data.DataSourceTest(t, []acceptance.TestStep{ { - Config: r.allocationType(data), + Config: r.staticAllocationType(data), }, { - Config: r.allocationTypeDataSources(data), + Config: r.staticAllocationTypeDataSources(data), Check: acceptance.ComposeTestCheckFunc( acceptance.TestCheckResourceAttr(staticDataSourceName, "public_ips.#", "3"), acceptance.TestCheckResourceAttr(staticDataSourceName, "public_ips.0.name", fmt.Sprintf("acctestpips%s-0", data.RandomString)), - acceptance.TestCheckResourceAttr(dynamicDataSourceName, "public_ips.#", "4"), - acceptance.TestCheckResourceAttr(dynamicDataSourceName, "public_ips.0.name", fmt.Sprintf("acctestpipd%s-0", data.RandomString)), ), }, }) @@ -200,7 +196,7 @@ data "azurerm_public_ips" "test" { `, r.prefix(data)) } -func (PublicIPsResource) allocationType(data acceptance.TestData) string { +func (PublicIPsResource) staticAllocationType(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -211,20 +207,6 @@ resource "azurerm_resource_group" "test" { location = "%s" } -resource "azurerm_public_ip" "dynamic" { - count = 4 - name = "acctestpipd%s-${count.index}" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - allocation_method = "Dynamic" - sku = "Basic" - idle_timeout_in_minutes = 30 - - tags = { - environment = "test" - } -} - resource "azurerm_public_ip" "static" { count = 3 name = "acctestpips%s-${count.index}" @@ -238,21 +220,16 @@ resource "azurerm_public_ip" "static" { environment = "test" } } -`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomString) +`, data.RandomInteger, data.Locations.Primary, data.RandomString) } -func (r PublicIPsResource) allocationTypeDataSources(data acceptance.TestData) string { +func (r PublicIPsResource) staticAllocationTypeDataSources(data acceptance.TestData) string { return fmt.Sprintf(` %s -data "azurerm_public_ips" "dynamic" { - resource_group_name = azurerm_resource_group.test.name - allocation_type = "Dynamic" -} - data "azurerm_public_ips" "static" { resource_group_name = azurerm_resource_group.test.name allocation_type = "Static" } -`, r.allocationType(data)) +`, r.staticAllocationType(data)) } diff --git a/website/docs/r/nat_gateway.html.markdown b/website/docs/r/nat_gateway.html.markdown index 8f6e35925907..f5e96a64da35 100644 --- a/website/docs/r/nat_gateway.html.markdown +++ b/website/docs/r/nat_gateway.html.markdown @@ -43,11 +43,11 @@ The following arguments are supported: * `sku_name` - (Optional) The SKU which should be used. Possible values are `Standard` and `StandardV2`. Defaults to `Standard`. Changing this forces a new resource to be created. -* `zones` - (Optional) A list of Availability Zones in which this NAT Gateway should be located. Changing this forces a new NAT Gateway to be created. +* `zones` - (Optional) A list of Availability Zones in which this NAT Gateway should be located. Changing this forces a new resource to be created. -> **Note:** For `Standard`, `zones` may be omitted for a no-zone deployment or set to a single Availability Zone. For more information, please see the [Azure documentation](https://learn.microsoft.com/azure/nat-gateway/nat-overview#availability-zones). -~> **Note:** For `StandardV2`, it is recommend to omit `zones`. The `StandardV2` NAT Gateway is zone-redundant by default, and Azure returns all available `zones` which may differ from the zones specified in config, see [MS learn documentation](https://learn.microsoft.com/azure/nat-gateway/nat-overview#standardv2-nat-gateway) for more info. +~> **Note:** `zones` must be omitted when `sku_name` is set to `StandardV2`. `StandardV2` NAT Gateways are zone-redundant by default and Azure automatically deploys across all available zones. For more information, please see the [Azure documentation](https://learn.microsoft.com/azure/nat-gateway/nat-overview#standardv2-nat-gateway). * `tags` - (Optional) A mapping of tags to assign to the resource.