diff --git a/modules/net-vlan-attachment/README.md b/modules/net-vlan-attachment/README.md
index 0ba2ed816..85a17c125 100644
--- a/modules/net-vlan-attachment/README.md
+++ b/modules/net-vlan-attachment/README.md
@@ -646,19 +646,20 @@ module "example-va-b" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [description](variables.tf#L36) | VLAN attachment description. | string | ✓ | |
-| [name](variables.tf#L53) | The common resources name, used after resource type prefix and suffix. | string | ✓ | |
-| [network](variables.tf#L58) | The VPC name to which resources are associated to. | string | ✓ | |
-| [peer_asn](variables.tf#L75) | The on-premises underlay router ASN. | string | ✓ | |
-| [project_id](variables.tf#L80) | The project id where resources are created. | string | ✓ | |
-| [region](variables.tf#L85) | The region where resources are created. | string | ✓ | |
-| [router_config](variables.tf#L90) | Cloud Router configuration for the VPN. If you want to reuse an existing router, set create to false and use name to specify the desired router. | object({…}) | ✓ | |
+| [description](variables.tf#L52) | VLAN attachment description. | string | ✓ | |
+| [name](variables.tf#L69) | The common resources name, used after resource type prefix and suffix. | string | ✓ | |
+| [network](variables.tf#L74) | The VPC name to which resources are associated to. | string | ✓ | |
+| [peer_asn](variables.tf#L91) | The on-premises underlay router ASN. | string | ✓ | |
+| [project_id](variables.tf#L96) | The project id where resources are created. | string | ✓ | |
+| [region](variables.tf#L101) | The region where resources are created. | string | ✓ | |
+| [router_config](variables.tf#L106) | Cloud Router configuration for the VPN. If you want to reuse an existing router, set create to false and use name to specify the desired router. | object({…}) | ✓ | |
| [admin_enabled](variables.tf#L17) | Whether the VLAN attachment is enabled. | bool | | true |
-| [dedicated_interconnect_config](variables.tf#L23) | Dedicated interconnect configuration. | object({…}) | | null |
-| [ipsec_gateway_ip_ranges](variables.tf#L41) | IPSec Gateway IP Ranges. | map(string) | | {} |
-| [mtu](variables.tf#L47) | The MTU associated to the VLAN attachment (1440 / 1500). | number | | 1500 |
-| [partner_interconnect_config](variables.tf#L63) | Partner interconnect configuration. | object({…}) | | null |
-| [vpn_gateways_ip_range](variables.tf#L115) | The IP range (cidr notation) to be used for the GCP VPN gateways. If null IPSec over Interconnect is not enabled. | string | | null |
+| [context](variables.tf#L23) | Context-specific interpolations. | object({…}) | | {} |
+| [dedicated_interconnect_config](variables.tf#L35) | Dedicated interconnect configuration. | object({…}) | | null |
+| [ipsec_gateway_ip_ranges](variables.tf#L57) | IPSec Gateway IP Ranges. | map(string) | | {} |
+| [mtu](variables.tf#L63) | The MTU associated to the VLAN attachment (1440 / 1500). | number | | 1500 |
+| [partner_interconnect_config](variables.tf#L79) | Partner interconnect configuration. | object({…}) | | null |
+| [vpn_gateways_ip_range](variables.tf#L131) | The IP range (cidr notation) to be used for the GCP VPN gateways. If null IPSec over Interconnect is not enabled. | string | | null |
## Outputs
diff --git a/modules/net-vlan-attachment/main.tf b/modules/net-vlan-attachment/main.tf
index 40b2c81f6..ab2680c00 100644
--- a/modules/net-vlan-attachment/main.tf
+++ b/modules/net-vlan-attachment/main.tf
@@ -15,20 +15,30 @@
*/
locals {
+ ctx_p = "$"
+ ctx = {
+ for k, v in var.context : k => {
+ for kk, vv in v : "${local.ctx_p}${k}:${kk}" => vv
+ }
+ }
ipsec_enabled = var.vpn_gateways_ip_range == null ? false : true
+ network = lookup(local.ctx.networks, var.network, var.network)
+ project_id = lookup(local.ctx.project_ids, var.project_id, var.project_id)
+ region = lookup(local.ctx.locations, var.region, var.region)
+ router_name = lookup(local.ctx.routers, try(var.router_config.name, ""), try(var.router_config.name, ""))
router = (
var.router_config.create
? local.ipsec_enabled ? try(google_compute_router.encrypted[0].name, null) : try(google_compute_router.unencrypted[0].name, null)
- : var.router_config.name
+ : local.router_name
)
secret = random_id.secret.b64_url
}
resource "google_compute_address" "default" {
count = local.ipsec_enabled ? 1 : 0
- project = var.project_id
- network = var.network
- region = var.region
+ project = local.project_id
+ network = local.network
+ region = local.region
name = "pool-${var.name}"
address_type = "INTERNAL"
purpose = "IPSEC_INTERCONNECT"
@@ -37,15 +47,15 @@ resource "google_compute_address" "default" {
}
resource "google_compute_interconnect_attachment" "default" {
- project = var.project_id
- region = var.region
+ project = local.project_id
+ region = local.region
router = local.router
name = var.name
description = var.description
interconnect = try(var.dedicated_interconnect_config.interconnect, null)
bandwidth = try(var.dedicated_interconnect_config.bandwidth, null)
mtu = local.ipsec_enabled ? null : var.mtu
- candidate_subnets = var.dedicated_interconnect_config != null ? [var.dedicated_interconnect_config.bgp_range] : null
+ candidate_subnets = try(var.dedicated_interconnect_config.bgp_range, null) != null ? [var.dedicated_interconnect_config.bgp_range] : null
vlan_tag8021q = try(var.dedicated_interconnect_config.vlan_tag, null)
admin_enabled = var.admin_enabled
encryption = local.ipsec_enabled ? "IPSEC" : null
@@ -57,9 +67,9 @@ resource "google_compute_interconnect_attachment" "default" {
resource "google_compute_router" "encrypted" {
count = var.router_config.create && local.ipsec_enabled ? 1 : 0
name = "${var.name}-underlay"
- network = var.network
- project = var.project_id
- region = var.region
+ network = local.network
+ project = local.project_id
+ region = local.region
encrypted_interconnect_router = true
bgp {
asn = var.router_config.asn
@@ -76,10 +86,10 @@ resource "google_compute_router" "encrypted" {
resource "google_compute_router" "unencrypted" {
count = var.router_config.create && !local.ipsec_enabled ? 1 : 0
- name = coalesce(var.router_config.name, "underlay-${var.name}")
- project = var.project_id
- region = var.region
- network = var.network
+ name = coalesce(local.router_name, "underlay-${var.name}")
+ project = local.project_id
+ region = local.region
+ network = local.network
bgp {
advertise_mode = (
var.router_config.custom_advertise != null
@@ -106,8 +116,8 @@ resource "google_compute_router" "unencrypted" {
resource "google_compute_router_interface" "default" {
count = var.dedicated_interconnect_config != null ? 1 : 0
- project = var.project_id
- region = var.region
+ project = local.project_id
+ region = local.region
name = "${var.name}-intf"
router = local.router
ip_range = google_compute_interconnect_attachment.default.cloud_router_ip_address
@@ -117,9 +127,9 @@ resource "google_compute_router_interface" "default" {
resource "google_compute_router_peer" "default" {
count = var.dedicated_interconnect_config != null ? 1 : 0
name = "${var.name}-peer"
- project = var.project_id
+ project = local.project_id
router = local.router
- region = var.region
+ region = local.region
peer_ip_address = split("/", google_compute_interconnect_attachment.default.customer_router_ip_address)[0]
peer_asn = var.peer_asn
interface = google_compute_router_interface.default[0].name
diff --git a/modules/net-vlan-attachment/variables.tf b/modules/net-vlan-attachment/variables.tf
index cd87b39c9..b4b317424 100644
--- a/modules/net-vlan-attachment/variables.tf
+++ b/modules/net-vlan-attachment/variables.tf
@@ -20,6 +20,18 @@ variable "admin_enabled" {
default = true
}
+variable "context" {
+ description = "Context-specific interpolations."
+ type = object({
+ locations = optional(map(string), {})
+ networks = optional(map(string), {})
+ project_ids = optional(map(string), {})
+ routers = optional(map(string), {})
+ })
+ default = {}
+ nullable = false
+}
+
variable "dedicated_interconnect_config" {
description = "Dedicated interconnect configuration."
type = object({
@@ -30,6 +42,10 @@ variable "dedicated_interconnect_config" {
interconnect = string
vlan_tag = string
})
+ validation {
+ condition = var.dedicated_interconnect_config == null ? true : contains(["BPS_50M", "BPS_100M", "BPS_200M", "BPS_300M", "BPS_400M", "BPS_500M", "BPS_1G", "BPS_2G", "BPS_5G", "BPS_10G", "BPS_20G", "BPS_50G", "BPS_100G", "BPS_400G"], var.dedicated_interconnect_config.bandwidth)
+ error_message = "The bandwidth must be one of BPS_50M, BPS_100M, BPS_200M, BPS_300M, BPS_400M, BPS_500M, BPS_1G, BPS_2G, BPS_5G, BPS_10G, BPS_20G, BPS_50G, BPS_100G, BPS_400G."
+ }
default = null
}
diff --git a/tests/modules/net-vlan-attachment/context.tfvars b/tests/modules/net-vlan-attachment/context.tfvars
new file mode 100644
index 000000000..59e8bfed9
--- /dev/null
+++ b/tests/modules/net-vlan-attachment/context.tfvars
@@ -0,0 +1,35 @@
+context = {
+ locations = {
+ my_region = "europe-west1"
+ }
+ networks = {
+ my_network = "projects/my-project/global/networks/my-network"
+ }
+ project_ids = {
+ my_project = "my-project"
+ }
+ routers = {
+ my_router = "my-router"
+ }
+}
+
+dedicated_interconnect_config = {
+ bandwidth = "BPS_100G"
+ bgp_range = "169.254.1.0/29"
+ bgp_priority = 100
+ interconnect = "projects/my-project/global/interconnects/my-interconnect"
+ vlan_tag = 100
+}
+
+description = "test attachment"
+name = "test-attachment"
+peer_asn = "65534"
+
+network = "$networks:my_network"
+project_id = "$project_ids:my_project"
+region = "$locations:my_region"
+
+router_config = {
+ create = false
+ name = "$routers:my_router"
+}
diff --git a/tests/modules/net-vlan-attachment/context.yaml b/tests/modules/net-vlan-attachment/context.yaml
new file mode 100644
index 000000000..e7ae61e7e
--- /dev/null
+++ b/tests/modules/net-vlan-attachment/context.yaml
@@ -0,0 +1,85 @@
+# Copyright 2025 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ google_compute_interconnect_attachment.default:
+ admin_enabled: true
+ bandwidth: BPS_100G
+ candidate_cloud_router_ip_address: null
+ candidate_cloud_router_ipv6_address: null
+ candidate_customer_router_ip_address: null
+ candidate_customer_router_ipv6_address: null
+ candidate_subnets:
+ - 169.254.1.0/29
+ description: test attachment
+ effective_labels:
+ goog-terraform-provisioned: 'true'
+ encryption: NONE
+ interconnect: projects/my-project/global/interconnects/my-interconnect
+ ipsec_internal_addresses: null
+ l2_forwarding: []
+ labels: null
+ mtu: '1500'
+ name: test-attachment
+ project: my-project
+ region: europe-west1
+ router: my-router
+ subnet_length: null
+ terraform_labels:
+ goog-terraform-provisioned: 'true'
+ timeouts: null
+ type: DEDICATED
+ vlan_tag8021q: 100
+ google_compute_router_interface.default[0]:
+ name: test-attachment-intf
+ private_ip_address: null
+ project: my-project
+ region: europe-west1
+ router: my-router
+ subnetwork: null
+ timeouts: null
+ vpn_tunnel: null
+ google_compute_router_peer.default[0]:
+ advertise_mode: CUSTOM
+ advertised_groups: null
+ advertised_ip_ranges: []
+ advertised_route_priority: 100
+ custom_learned_ip_ranges: []
+ custom_learned_route_priority: null
+ enable: true
+ enable_ipv6: false
+ export_policies: null
+ import_policies: null
+ interface: test-attachment-intf
+ md5_authentication_key: []
+ name: test-attachment-peer
+ peer_asn: 65534
+ project: my-project
+ region: europe-west1
+ router: my-router
+ router_appliance_instance: null
+ timeouts: null
+ zero_advertised_route_priority: null
+ zero_custom_learned_route_priority: false
+ random_id.secret:
+ byte_length: 12
+ keepers: null
+ prefix: null
+
+counts:
+ google_compute_interconnect_attachment: 1
+ google_compute_router_interface: 1
+ google_compute_router_peer: 1
+ modules: 0
+ random_id: 1
diff --git a/tests/modules/net-vlan-attachment/tftest.yaml b/tests/modules/net-vlan-attachment/tftest.yaml
new file mode 100644
index 000000000..b38fadbc3
--- /dev/null
+++ b/tests/modules/net-vlan-attachment/tftest.yaml
@@ -0,0 +1,17 @@
+# Copyright 2025 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module: modules/net-vlan-attachment
+tests:
+ context: