From 872293dcc5521a80d063a2099feba51aaec299fc Mon Sep 17 00:00:00 2001 From: Alex Karpenko Date: Tue, 17 Feb 2026 22:48:21 +0000 Subject: [PATCH 1/8] azurerm_virtual_network_gateway: restore ExpressRoute legacy public_ip_address_id behavior --- .../virtual_network_gateway_resource.go | 14 ++--- ...tual_network_gateway_resource_unit_test.go | 57 +++++++++++++++++++ .../r/virtual_network_gateway.html.markdown | 2 +- 3 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 internal/services/network/virtual_network_gateway_resource_unit_test.go diff --git a/internal/services/network/virtual_network_gateway_resource.go b/internal/services/network/virtual_network_gateway_resource.go index dd47f91ff4ef..d9b58d53592b 100644 --- a/internal/services/network/virtual_network_gateway_resource.go +++ b/internal/services/network/virtual_network_gateway_resource.go @@ -789,8 +789,7 @@ func resourceVirtualNetworkGatewayRead(d *pluginsdk.ResourceData, meta interface d.Set("sku", string(pointer.From(props.Sku.Name))) } - gatewayType := pointer.From(props.GatewayType) - if err := d.Set("ip_configuration", flattenVirtualNetworkGatewayIPConfigurations(props.IPConfigurations, gatewayType)); err != nil { + if err := d.Set("ip_configuration", flattenVirtualNetworkGatewayIPConfigurations(props.IPConfigurations)); err != nil { return fmt.Errorf("setting `ip_configuration`: %+v", err) } @@ -1427,7 +1426,7 @@ func flattenVirtualNetworkGatewayBgpPeeringAddresses(input *[]virtualnetworkgate return output, nil } -func flattenVirtualNetworkGatewayIPConfigurations(ipConfigs *[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration, gatewayType virtualnetworkgateways.VirtualNetworkGatewayType) []interface{} { +func flattenVirtualNetworkGatewayIPConfigurations(ipConfigs *[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration) []interface{} { flat := make([]interface{}, 0) if ipConfigs != nil { @@ -1446,12 +1445,9 @@ func flattenVirtualNetworkGatewayIPConfigurations(ipConfigs *[]virtualnetworkgat } } - // Do not include public_ip_address_id for ExpressRoute gateways - if gatewayType != virtualnetworkgateways.VirtualNetworkGatewayTypeExpressRoute { - if pip := props.PublicIPAddress; pip != nil { - if id := pip.Id; id != nil { - v["public_ip_address_id"] = *id - } + if pip := props.PublicIPAddress; pip != nil { + if id := pip.Id; id != nil { + v["public_ip_address_id"] = *id } } diff --git a/internal/services/network/virtual_network_gateway_resource_unit_test.go b/internal/services/network/virtual_network_gateway_resource_unit_test.go new file mode 100644 index 000000000000..6b9e54c50f58 --- /dev/null +++ b/internal/services/network/virtual_network_gateway_resource_unit_test.go @@ -0,0 +1,57 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package network + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-sdk/resource-manager/network/2025-01-01/virtualnetworkgateways" +) + +func TestVirtualNetworkGatewayResource_NoCustomizeDiff(t *testing.T) { + r := resourceVirtualNetworkGateway() + + if r.CustomizeDiff != nil { + t.Fatalf("expected CustomizeDiff to be nil") + } +} + +func TestFlattenVirtualNetworkGatewayIPConfigurations_IncludesPublicIPAddress(t *testing.T) { + input := &[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration{ + { + Name: pointer.To("vnetGatewayConfig"), + Properties: &virtualnetworkgateways.VirtualNetworkGatewayIPConfigurationPropertiesFormat{ + PrivateIPAllocationMethod: pointer.To(virtualnetworkgateways.IPAllocationMethodDynamic), + Subnet: &virtualnetworkgateways.SubResource{ + Id: pointer.To("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vnet/subnets/GatewaySubnet"), + }, + PublicIPAddress: &virtualnetworkgateways.SubResource{ + Id: pointer.To("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip"), + }, + }, + }, + } + + output := flattenVirtualNetworkGatewayIPConfigurations(input) + if len(output) != 1 { + t.Fatalf("expected one flattened ip_configuration but got %d", len(output)) + } + + config, ok := output[0].(map[string]interface{}) + if !ok { + t.Fatalf("expected flattened item to be a map[string]interface{}") + } + + publicIPID, ok := config["public_ip_address_id"].(string) + if !ok { + t.Fatalf("expected public_ip_address_id to be present in flattened output") + } + + expectedPublicIPID := "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip" + if publicIPID != expectedPublicIPID { + t.Fatalf("expected public_ip_address_id %q but got %q", expectedPublicIPID, publicIPID) + } +} + diff --git a/website/docs/r/virtual_network_gateway.html.markdown b/website/docs/r/virtual_network_gateway.html.markdown index 5795faa9a7ba..b5821e655446 100644 --- a/website/docs/r/virtual_network_gateway.html.markdown +++ b/website/docs/r/virtual_network_gateway.html.markdown @@ -170,7 +170,7 @@ The `ip_configuration` block supports: * `public_ip_address_id` - (Optional) The ID of the public IP address to associate with the Virtual Network Gateway. -~> **Note:** `public_ip_address_id` should not be specified when `type` is set to `ExpressRoute`. +~> **Note:** For `ExpressRoute` gateways, Azure may use auto-assigned public IPs (HOBO) for newer deployments. Existing gateways can still expose a `public_ip_address_id`. --- From d8e398030a7a48c52ec308231cb9cc583b9d2b08 Mon Sep 17 00:00:00 2001 From: Alex Karpenko Date: Tue, 17 Feb 2026 23:31:24 +0000 Subject: [PATCH 2/8] azurerm_virtual_network_gateway: expand unit coverage for IP configuration flattening --- ...tual_network_gateway_resource_unit_test.go | 123 ++++++++++++++---- 1 file changed, 98 insertions(+), 25 deletions(-) diff --git a/internal/services/network/virtual_network_gateway_resource_unit_test.go b/internal/services/network/virtual_network_gateway_resource_unit_test.go index 6b9e54c50f58..69caaccd1d29 100644 --- a/internal/services/network/virtual_network_gateway_resource_unit_test.go +++ b/internal/services/network/virtual_network_gateway_resource_unit_test.go @@ -4,6 +4,7 @@ package network import ( + "reflect" "testing" "github.com/hashicorp/go-azure-helpers/lang/pointer" @@ -18,40 +19,112 @@ func TestVirtualNetworkGatewayResource_NoCustomizeDiff(t *testing.T) { } } -func TestFlattenVirtualNetworkGatewayIPConfigurations_IncludesPublicIPAddress(t *testing.T) { - input := &[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration{ +func TestFlattenVirtualNetworkGatewayIPConfigurations(t *testing.T) { + subnetID := "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vnet/subnets/GatewaySubnet" + publicIPID := "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip" + + tests := []struct { + name string + input *[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration + want []map[string]interface{} + }{ + { + name: "nil input returns empty output", + input: nil, + want: []map[string]interface{}{}, + }, + { + name: "empty input returns empty output", + input: &[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration{}, + want: []map[string]interface{}{}, + }, + { + name: "includes public ip address id", + input: &[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration{ + { + Name: pointer.To("vnetGatewayConfig"), + Properties: &virtualnetworkgateways.VirtualNetworkGatewayIPConfigurationPropertiesFormat{ + PrivateIPAllocationMethod: pointer.To(virtualnetworkgateways.IPAllocationMethodDynamic), + Subnet: &virtualnetworkgateways.SubResource{ + Id: pointer.To(subnetID), + }, + PublicIPAddress: &virtualnetworkgateways.SubResource{ + Id: pointer.To(publicIPID), + }, + }, + }, + }, + want: []map[string]interface{}{ + { + "name": "vnetGatewayConfig", + "private_ip_address_allocation": "Dynamic", + "subnet_id": subnetID, + "public_ip_address_id": publicIPID, + }, + }, + }, + { + name: "omits optional fields when ids and name are absent", + input: &[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration{ + { + Properties: &virtualnetworkgateways.VirtualNetworkGatewayIPConfigurationPropertiesFormat{ + PrivateIPAllocationMethod: pointer.To(virtualnetworkgateways.IPAllocationMethodStatic), + }, + }, + }, + want: []map[string]interface{}{ + { + "private_ip_address_allocation": "Static", + }, + }, + }, { - Name: pointer.To("vnetGatewayConfig"), - Properties: &virtualnetworkgateways.VirtualNetworkGatewayIPConfigurationPropertiesFormat{ - PrivateIPAllocationMethod: pointer.To(virtualnetworkgateways.IPAllocationMethodDynamic), - Subnet: &virtualnetworkgateways.SubResource{ - Id: pointer.To("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vnet/subnets/GatewaySubnet"), + name: "preserves order for multiple configurations", + input: &[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration{ + { + Name: pointer.To("first"), + Properties: &virtualnetworkgateways.VirtualNetworkGatewayIPConfigurationPropertiesFormat{ + PrivateIPAllocationMethod: pointer.To(virtualnetworkgateways.IPAllocationMethodDynamic), + }, + }, + { + Name: pointer.To("second"), + Properties: &virtualnetworkgateways.VirtualNetworkGatewayIPConfigurationPropertiesFormat{ + PrivateIPAllocationMethod: pointer.To(virtualnetworkgateways.IPAllocationMethodStatic), + }, }, - PublicIPAddress: &virtualnetworkgateways.SubResource{ - Id: pointer.To("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip"), + }, + want: []map[string]interface{}{ + { + "name": "first", + "private_ip_address_allocation": "Dynamic", + }, + { + "name": "second", + "private_ip_address_allocation": "Static", }, }, }, } - output := flattenVirtualNetworkGatewayIPConfigurations(input) - if len(output) != 1 { - t.Fatalf("expected one flattened ip_configuration but got %d", len(output)) - } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + gotRaw := flattenVirtualNetworkGatewayIPConfigurations(tt.input) + got := make([]map[string]interface{}, 0, len(gotRaw)) - config, ok := output[0].(map[string]interface{}) - if !ok { - t.Fatalf("expected flattened item to be a map[string]interface{}") - } + for i, item := range gotRaw { + cfg, ok := item.(map[string]interface{}) + if !ok { + t.Fatalf("expected flattened item at index %d to be a map[string]interface{}", i) + } - publicIPID, ok := config["public_ip_address_id"].(string) - if !ok { - t.Fatalf("expected public_ip_address_id to be present in flattened output") - } + got = append(got, cfg) + } - expectedPublicIPID := "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip" - if publicIPID != expectedPublicIPID { - t.Fatalf("expected public_ip_address_id %q but got %q", expectedPublicIPID, publicIPID) + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("unexpected flattened output: got %#v, want %#v", got, tt.want) + } + }) } } - From cc68addce68a01a4622e5a990d875e546a037727 Mon Sep 17 00:00:00 2001 From: Alex Karpenko Date: Thu, 5 Mar 2026 21:58:45 +0000 Subject: [PATCH 3/8] addressing comments --- .../virtual_network_gateway_resource_test.go | 63 +++++++++ ...tual_network_gateway_resource_unit_test.go | 130 ------------------ .../r/virtual_network_gateway.html.markdown | 2 +- 3 files changed, 64 insertions(+), 131 deletions(-) delete mode 100644 internal/services/network/virtual_network_gateway_resource_unit_test.go diff --git a/internal/services/network/virtual_network_gateway_resource_test.go b/internal/services/network/virtual_network_gateway_resource_test.go index 3ee72815ecec..3affb78d2ae1 100644 --- a/internal/services/network/virtual_network_gateway_resource_test.go +++ b/internal/services/network/virtual_network_gateway_resource_test.go @@ -355,6 +355,18 @@ func TestAccVirtualNetworkGateway_expressRoute(t *testing.T) { }) } +func TestAccVirtualNetworkGateway_expressRouteWithPublicIPAddressId(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_virtual_network_gateway", "test") + r := VirtualNetworkGatewayResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.expressRouteWithPublicIPAddressId(data), + PlanOnly: true, + }, + }) +} + func TestAccVirtualNetworkGateway_expressRouteErGwScale(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_virtual_network_gateway", "test") r := VirtualNetworkGatewayResource{} @@ -1420,6 +1432,57 @@ resource "azurerm_virtual_network_gateway" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) } +func (VirtualNetworkGatewayResource) expressRouteWithPublicIPAddressId(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvn-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + address_space = ["10.0.0.0/16"] +} + +resource "azurerm_subnet" "test" { + name = "GatewaySubnet" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.0.1.0/24"] +} + +resource "azurerm_public_ip" "test" { + name = "acctestpip1-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + allocation_method = "Static" + sku = "Standard" +} + +resource "azurerm_virtual_network_gateway" "test" { + name = "acctestvng-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + type = "ExpressRoute" + vpn_type = "PolicyBased" + sku = "Standard" + + ip_configuration { + public_ip_address_id = azurerm_public_ip.test.id + private_ip_address_allocation = "Dynamic" + subnet_id = azurerm_subnet.test.id + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) +} + func (VirtualNetworkGatewayResource) expressRouteErGwScale(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/internal/services/network/virtual_network_gateway_resource_unit_test.go b/internal/services/network/virtual_network_gateway_resource_unit_test.go deleted file mode 100644 index 69caaccd1d29..000000000000 --- a/internal/services/network/virtual_network_gateway_resource_unit_test.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright IBM Corp. 2014, 2026 -// SPDX-License-Identifier: MPL-2.0 - -package network - -import ( - "reflect" - "testing" - - "github.com/hashicorp/go-azure-helpers/lang/pointer" - "github.com/hashicorp/go-azure-sdk/resource-manager/network/2025-01-01/virtualnetworkgateways" -) - -func TestVirtualNetworkGatewayResource_NoCustomizeDiff(t *testing.T) { - r := resourceVirtualNetworkGateway() - - if r.CustomizeDiff != nil { - t.Fatalf("expected CustomizeDiff to be nil") - } -} - -func TestFlattenVirtualNetworkGatewayIPConfigurations(t *testing.T) { - subnetID := "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vnet/subnets/GatewaySubnet" - publicIPID := "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip" - - tests := []struct { - name string - input *[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration - want []map[string]interface{} - }{ - { - name: "nil input returns empty output", - input: nil, - want: []map[string]interface{}{}, - }, - { - name: "empty input returns empty output", - input: &[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration{}, - want: []map[string]interface{}{}, - }, - { - name: "includes public ip address id", - input: &[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration{ - { - Name: pointer.To("vnetGatewayConfig"), - Properties: &virtualnetworkgateways.VirtualNetworkGatewayIPConfigurationPropertiesFormat{ - PrivateIPAllocationMethod: pointer.To(virtualnetworkgateways.IPAllocationMethodDynamic), - Subnet: &virtualnetworkgateways.SubResource{ - Id: pointer.To(subnetID), - }, - PublicIPAddress: &virtualnetworkgateways.SubResource{ - Id: pointer.To(publicIPID), - }, - }, - }, - }, - want: []map[string]interface{}{ - { - "name": "vnetGatewayConfig", - "private_ip_address_allocation": "Dynamic", - "subnet_id": subnetID, - "public_ip_address_id": publicIPID, - }, - }, - }, - { - name: "omits optional fields when ids and name are absent", - input: &[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration{ - { - Properties: &virtualnetworkgateways.VirtualNetworkGatewayIPConfigurationPropertiesFormat{ - PrivateIPAllocationMethod: pointer.To(virtualnetworkgateways.IPAllocationMethodStatic), - }, - }, - }, - want: []map[string]interface{}{ - { - "private_ip_address_allocation": "Static", - }, - }, - }, - { - name: "preserves order for multiple configurations", - input: &[]virtualnetworkgateways.VirtualNetworkGatewayIPConfiguration{ - { - Name: pointer.To("first"), - Properties: &virtualnetworkgateways.VirtualNetworkGatewayIPConfigurationPropertiesFormat{ - PrivateIPAllocationMethod: pointer.To(virtualnetworkgateways.IPAllocationMethodDynamic), - }, - }, - { - Name: pointer.To("second"), - Properties: &virtualnetworkgateways.VirtualNetworkGatewayIPConfigurationPropertiesFormat{ - PrivateIPAllocationMethod: pointer.To(virtualnetworkgateways.IPAllocationMethodStatic), - }, - }, - }, - want: []map[string]interface{}{ - { - "name": "first", - "private_ip_address_allocation": "Dynamic", - }, - { - "name": "second", - "private_ip_address_allocation": "Static", - }, - }, - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - gotRaw := flattenVirtualNetworkGatewayIPConfigurations(tt.input) - got := make([]map[string]interface{}, 0, len(gotRaw)) - - for i, item := range gotRaw { - cfg, ok := item.(map[string]interface{}) - if !ok { - t.Fatalf("expected flattened item at index %d to be a map[string]interface{}", i) - } - - got = append(got, cfg) - } - - if !reflect.DeepEqual(got, tt.want) { - t.Fatalf("unexpected flattened output: got %#v, want %#v", got, tt.want) - } - }) - } -} diff --git a/website/docs/r/virtual_network_gateway.html.markdown b/website/docs/r/virtual_network_gateway.html.markdown index b5821e655446..b451e08b5b14 100644 --- a/website/docs/r/virtual_network_gateway.html.markdown +++ b/website/docs/r/virtual_network_gateway.html.markdown @@ -170,7 +170,7 @@ The `ip_configuration` block supports: * `public_ip_address_id` - (Optional) The ID of the public IP address to associate with the Virtual Network Gateway. -~> **Note:** For `ExpressRoute` gateways, Azure may use auto-assigned public IPs (HOBO) for newer deployments. Existing gateways can still expose a `public_ip_address_id`. +~> **Note:** For `ExpressRoute` gateways, Azure may use auto-assigned Hosted-On-Behalf-Of (HOBO) public IPs for newer deployments. Existing gateways can still expose a `public_ip_address_id`. For more information, see [Create a virtual network gateway for ExpressRoute](https://learn.microsoft.com/en-us/azure/expressroute/create-expressroute-vnet-gateway). --- From 1e69507519bb521cc3ec3eb6ae14cfbb13f32dd6 Mon Sep 17 00:00:00 2001 From: Alex Karpenko Date: Thu, 5 Mar 2026 22:01:44 +0000 Subject: [PATCH 4/8] documentation link fix --- website/docs/r/virtual_network_gateway.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/virtual_network_gateway.html.markdown b/website/docs/r/virtual_network_gateway.html.markdown index b451e08b5b14..d68f227c95cc 100644 --- a/website/docs/r/virtual_network_gateway.html.markdown +++ b/website/docs/r/virtual_network_gateway.html.markdown @@ -170,7 +170,7 @@ The `ip_configuration` block supports: * `public_ip_address_id` - (Optional) The ID of the public IP address to associate with the Virtual Network Gateway. -~> **Note:** For `ExpressRoute` gateways, Azure may use auto-assigned Hosted-On-Behalf-Of (HOBO) public IPs for newer deployments. Existing gateways can still expose a `public_ip_address_id`. For more information, see [Create a virtual network gateway for ExpressRoute](https://learn.microsoft.com/en-us/azure/expressroute/create-expressroute-vnet-gateway). +~> **Note:** For `ExpressRoute` gateways, Azure may use auto-assigned Hosted-On-Behalf-Of (HOBO) public IPs for newer deployments. Existing gateways can still expose a `public_ip_address_id`. For more information, see [Auto-assigned public IP](https://learn.microsoft.com/en-us/azure/expressroute/expressroute-about-virtual-network-gateways#auto-assigned-public-ip). --- From b149afe6feb63520dc6a08daf4ca0e391550cd8b Mon Sep 17 00:00:00 2001 From: Alex Karpenko Date: Thu, 5 Mar 2026 23:00:14 +0000 Subject: [PATCH 5/8] post merge conflic resolution fix --- .../virtual_network_gateway_resource.go | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/internal/services/network/virtual_network_gateway_resource.go b/internal/services/network/virtual_network_gateway_resource.go index d9b58d53592b..cfa51f80bef2 100644 --- a/internal/services/network/virtual_network_gateway_resource.go +++ b/internal/services/network/virtual_network_gateway_resource.go @@ -5,7 +5,6 @@ package network import ( "bytes" - "context" "fmt" "log" "math" @@ -49,8 +48,6 @@ func resourceVirtualNetworkGateway() *pluginsdk.Resource { Delete: pluginsdk.DefaultTimeout(120 * time.Minute), }, - CustomizeDiff: pluginsdk.CustomizeDiffShim(resourceVirtualNetworkGatewayCustomizeDiff), - Schema: map[string]*pluginsdk.Schema{ "name": { Type: pluginsdk.TypeString, @@ -674,23 +671,6 @@ func resourceVirtualNetworkGateway() *pluginsdk.Resource { return resource } -func resourceVirtualNetworkGatewayCustomizeDiff(ctx context.Context, d *pluginsdk.ResourceDiff, _ interface{}) error { - gatewayType := d.Get("type").(string) - - // Validate that public_ip_address_id is not set for ExpressRoute gateways - if gatewayType == string(virtualnetworkgateways.VirtualNetworkGatewayTypeExpressRoute) { - ipConfigs := d.Get("ip_configuration").([]interface{}) - for i, ipConfigRaw := range ipConfigs { - ipConfig := ipConfigRaw.(map[string]interface{}) - if publicIPID, ok := ipConfig["public_ip_address_id"].(string); ok && publicIPID != "" { - return fmt.Errorf("`ip_configuration.%d.public_ip_address_id` cannot be set when `type` is set to `ExpressRoute`", i) - } - } - } - - return nil -} - func resourceVirtualNetworkGatewayCreate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.VirtualNetworkGateways ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) From 8be200cc9e6f5490046ac4efcb7f93d6f1601f72 Mon Sep 17 00:00:00 2001 From: Alex Karpenko Date: Thu, 5 Mar 2026 23:49:51 +0000 Subject: [PATCH 6/8] brownfield gateway test added --- .../virtual_network_gateway_resource_test.go | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/internal/services/network/virtual_network_gateway_resource_test.go b/internal/services/network/virtual_network_gateway_resource_test.go index 3affb78d2ae1..097e39932ea3 100644 --- a/internal/services/network/virtual_network_gateway_resource_test.go +++ b/internal/services/network/virtual_network_gateway_resource_test.go @@ -6,6 +6,7 @@ package network_test import ( "context" "fmt" + "os" "testing" "github.com/hashicorp/go-azure-helpers/lang/pointer" @@ -358,10 +359,29 @@ func TestAccVirtualNetworkGateway_expressRoute(t *testing.T) { func TestAccVirtualNetworkGateway_expressRouteWithPublicIPAddressId(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_virtual_network_gateway", "test") r := VirtualNetworkGatewayResource{} + legacyGatewayID := os.Getenv("ARM_TEST_LEGACY_EXPRESSROUTE_GATEWAY_ID") + + if legacyGatewayID == "" { + t.Skip("Skipping as `ARM_TEST_LEGACY_EXPRESSROUTE_GATEWAY_ID` was not specified") + } data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.expressRouteWithPublicIPAddressId(data), + Config: r.expressRouteWithPublicIPAddressIdBrownfield(legacyGatewayID), + ResourceName: data.ResourceName, + ImportState: true, + ImportStateId: legacyGatewayID, + ImportStateVerify: true, + }, + { + Config: r.expressRouteWithPublicIPAddressIdBrownfield(legacyGatewayID), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("type").HasValue("ExpressRoute"), + check.That(data.ResourceName).Key("ip_configuration.0.public_ip_address_id").Exists(), + ), + }, + { + Config: r.expressRouteWithPublicIPAddressIdBrownfield(legacyGatewayID), PlanOnly: true, }, }) @@ -1432,55 +1452,41 @@ resource "azurerm_virtual_network_gateway" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) } -func (VirtualNetworkGatewayResource) expressRouteWithPublicIPAddressId(data acceptance.TestData) string { +func (VirtualNetworkGatewayResource) expressRouteWithPublicIPAddressIdBrownfield(existingGatewayID string) string { return fmt.Sprintf(` provider "azurerm" { features {} } -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" +locals { + gateway_id = "%s" + gateway_name = split("/", local.gateway_id)[8] + resource_group_name = split("/", local.gateway_id)[4] } -resource "azurerm_virtual_network" "test" { - name = "acctestvn-%d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - address_space = ["10.0.0.0/16"] -} - -resource "azurerm_subnet" "test" { - name = "GatewaySubnet" - resource_group_name = azurerm_resource_group.test.name - virtual_network_name = azurerm_virtual_network.test.name - address_prefixes = ["10.0.1.0/24"] -} - -resource "azurerm_public_ip" "test" { - name = "acctestpip1-%d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - allocation_method = "Static" - sku = "Standard" +data "azurerm_virtual_network_gateway" "existing" { + name = local.gateway_name + resource_group_name = local.resource_group_name } resource "azurerm_virtual_network_gateway" "test" { - name = "acctestvng-%d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name + name = local.gateway_name + location = data.azurerm_virtual_network_gateway.existing.location + resource_group_name = local.resource_group_name type = "ExpressRoute" - vpn_type = "PolicyBased" - sku = "Standard" + vpn_type = data.azurerm_virtual_network_gateway.existing.vpn_type + sku = data.azurerm_virtual_network_gateway.existing.sku + generation = data.azurerm_virtual_network_gateway.existing.generation ip_configuration { - public_ip_address_id = azurerm_public_ip.test.id - private_ip_address_allocation = "Dynamic" - subnet_id = azurerm_subnet.test.id + name = data.azurerm_virtual_network_gateway.existing.ip_configuration[0].name + public_ip_address_id = data.azurerm_virtual_network_gateway.existing.ip_configuration[0].public_ip_address_id + private_ip_address_allocation = data.azurerm_virtual_network_gateway.existing.ip_configuration[0].private_ip_address_allocation + subnet_id = data.azurerm_virtual_network_gateway.existing.ip_configuration[0].subnet_id } } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) +`, existingGatewayID) } func (VirtualNetworkGatewayResource) expressRouteErGwScale(data acceptance.TestData) string { From c9a4eb3f57eb7542beb4191587b30d712d324a22 Mon Sep 17 00:00:00 2001 From: Alex Karpenko Date: Sun, 22 Mar 2026 13:15:59 +0000 Subject: [PATCH 7/8] network: clarify legacy ER gateway acctest requirements --- .../network/virtual_network_gateway_resource_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/services/network/virtual_network_gateway_resource_test.go b/internal/services/network/virtual_network_gateway_resource_test.go index 097e39932ea3..e4715e6ecf50 100644 --- a/internal/services/network/virtual_network_gateway_resource_test.go +++ b/internal/services/network/virtual_network_gateway_resource_test.go @@ -359,6 +359,10 @@ func TestAccVirtualNetworkGateway_expressRoute(t *testing.T) { func TestAccVirtualNetworkGateway_expressRouteWithPublicIPAddressId(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_virtual_network_gateway", "test") r := VirtualNetworkGatewayResource{} + // Brownfield-only: this must be an existing legacy ExpressRoute gateway that still + // returns `ip_configuration.0.public_ip_address_id`. Microsoft made auto-assigned + // Public IPs generally available in July 2025; gateways created since then use the + // HOBO model and do not expose that field. legacyGatewayID := os.Getenv("ARM_TEST_LEGACY_EXPRESSROUTE_GATEWAY_ID") if legacyGatewayID == "" { @@ -380,10 +384,6 @@ func TestAccVirtualNetworkGateway_expressRouteWithPublicIPAddressId(t *testing.T check.That(data.ResourceName).Key("ip_configuration.0.public_ip_address_id").Exists(), ), }, - { - Config: r.expressRouteWithPublicIPAddressIdBrownfield(legacyGatewayID), - PlanOnly: true, - }, }) } From 1d6bda458f12a2549a812c3207364d2e7d08e79c Mon Sep 17 00:00:00 2001 From: Alex Karpenko Date: Wed, 25 Mar 2026 21:39:00 +0000 Subject: [PATCH 8/8] import step --- .../services/network/virtual_network_gateway_resource_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/services/network/virtual_network_gateway_resource_test.go b/internal/services/network/virtual_network_gateway_resource_test.go index e4715e6ecf50..1428887fbce8 100644 --- a/internal/services/network/virtual_network_gateway_resource_test.go +++ b/internal/services/network/virtual_network_gateway_resource_test.go @@ -384,6 +384,7 @@ func TestAccVirtualNetworkGateway_expressRouteWithPublicIPAddressId(t *testing.T check.That(data.ResourceName).Key("ip_configuration.0.public_ip_address_id").Exists(), ), }, + data.ImportStep(), }) }