From c33a4b57c1e5223035fc20e0e29c0c298e94f15e Mon Sep 17 00:00:00 2001 From: Chris McCoy Date: Mon, 16 Jun 2025 22:06:47 -0400 Subject: [PATCH] Add support for IPv6 only subnets and IP collections --- modules/net-vpc/README.md | 52 ++++++++++-- modules/net-vpc/schemas/subnet.schema.json | 6 ++ modules/net-vpc/schemas/subnet.schema.md | 2 + modules/net-vpc/subnets.tf | 33 ++++++-- modules/net-vpc/variables.tf | 2 + tests/modules/net_vpc/examples/ipv6_only.yaml | 82 +++++++++++++++++++ 6 files changed, 164 insertions(+), 13 deletions(-) create mode 100644 tests/modules/net_vpc/examples/ipv6_only.yaml diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 62d298d69..2968c54db 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -23,6 +23,7 @@ This module allows creation and management of VPC networks including subnetworks - [Private Google Access routes](#private-google-access-routes) - [Allow Firewall Policy to be evaluated before Firewall Rules](#allow-firewall-policy-to-be-evaluated-before-firewall-rules) - [IPv6](#ipv6) + - [IPv6-Only and IP Collections](#ipv6-only-and-ip-collections) - [Variables](#variables) - [Outputs](#outputs) @@ -712,6 +713,47 @@ module "vpc" { } # tftest modules=1 resources=6 inventory=ipv6.yaml e2e ``` + +### IPv6-Only and IP Collections + +An IPv6-only subnetwork can be specified by setting `ipv6_only` to `true` and +setting `ip_cidr_range` to `null`. An IP Collection may be specified with +`ip_collection` and a +[reference](https://cloud.google.com/compute/docs/reference/rest/v1/subnetworks/insert) +to a collection source, like a PublicDelegatedPrefix (PDP) for BYOIPv6. The PDP +must be a sub-PDP in `EXTERNAL_IPV6_SUBNETWORK_CREATION` mode. + +```hcl +module "vpc" { + source = "./fabric/modules/net-vpc" + project_id = var.project_id + name = "my-network" + ipv6_config = { + enable_ula_internal = true + } + subnets = [ + { + ip_cidr_range = null + name = "test-v6only" + region = "europe-west1" + ipv6 = { + ipv6_only = true + } + }, + { + ip_cidr_range = null + name = "test-v6only" + region = "europe-west3" + ipv6 = { + access_type = "EXTERNAL" + ipv6_only = true + } + ip_collection = "https://www.googleapis.com/compute/v1/projects/project-id/regions/europe-west3/publicDelegatedPrefixes/test-sub-pdp" + } + ] +} +# tftest modules=1 resources=6 inventory=ipv6_only.yaml e2e +``` ## Variables @@ -736,11 +778,11 @@ module "vpc" { | [routing_mode](variables.tf#L234) | The network routing mode (default 'GLOBAL'). | string | | "GLOBAL" | | [shared_vpc_host](variables.tf#L244) | Enable shared VPC for this project. | bool | | false | | [shared_vpc_service_projects](variables.tf#L250) | Shared VPC service projects to register with this host. | list(string) | | [] | -| [subnets](variables.tf#L256) | Subnet configuration. | list(object({…})) | | [] | -| [subnets_private_nat](variables.tf#L303) | List of private NAT subnets. | list(object({…})) | | [] | -| [subnets_proxy_only](variables.tf#L315) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…})) | | [] | -| [subnets_psc](variables.tf#L349) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | -| [vpc_create](variables.tf#L381) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool | | true | +| [subnets](variables.tf#L256) | Subnet configuration. | list(object({…})) | | [] | +| [subnets_private_nat](variables.tf#L305) | List of private NAT subnets. | list(object({…})) | | [] | +| [subnets_proxy_only](variables.tf#L317) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…})) | | [] | +| [subnets_psc](variables.tf#L351) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | +| [vpc_create](variables.tf#L383) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool | | true | ## Outputs diff --git a/modules/net-vpc/schemas/subnet.schema.json b/modules/net-vpc/schemas/subnet.schema.json index f8cb7d55d..48f8093c6 100644 --- a/modules/net-vpc/schemas/subnet.schema.json +++ b/modules/net-vpc/schemas/subnet.schema.json @@ -56,9 +56,15 @@ "properties": { "access_type": { "type": "string" + }, + "ipv6_only": { + "type": "boolean" } } }, + "ip_collection": { + "type": "string" + }, "name": { "type": "string" }, diff --git a/modules/net-vpc/schemas/subnet.schema.md b/modules/net-vpc/schemas/subnet.schema.md index 66286ef8e..0022471c6 100644 --- a/modules/net-vpc/schemas/subnet.schema.md +++ b/modules/net-vpc/schemas/subnet.schema.md @@ -23,6 +23,8 @@ - **ipv6**: *object*
*additional properties: false* - **access_type**: *string* + - +**ipv6_only**: *boolean* +- ⁺**ip_collection**: *string* - **name**: *string* - ⁺**region**: *string* - **psc**: *boolean* diff --git a/modules/net-vpc/subnets.tf b/modules/net-vpc/subnets.tf index a75f5b7b9..6f14de997 100644 --- a/modules/net-vpc/subnets.tf +++ b/modules/net-vpc/subnets.tf @@ -43,7 +43,9 @@ locals { ip_cidr_range = v.ip_cidr_range ipv6 = !can(v.ipv6) ? null : { access_type = try(v.ipv6.access_type, "INTERNAL") + ipv6_only = try(v.ipv6.ipv6_only, false) } + ip_collection = try(v.ip_collection, null) name = try(v.name, k) region = v.region_computed secondary_ip_ranges = try(v.secondary_ip_ranges, null) @@ -139,13 +141,21 @@ locals { } resource "google_compute_subnetwork" "subnetwork" { - provider = google-beta - for_each = local.subnets - project = var.project_id - network = local.network.name - name = each.value.name - region = each.value.region - ip_cidr_range = each.value.ip_cidr_range + provider = google-beta + for_each = local.subnets + project = var.project_id + network = local.network.name + name = each.value.name + region = each.value.region + ip_cidr_range = ( + try(each.value.ipv6, null) != null + ? ( + try(each.value.ipv6.ipv6_only, false) + ? null + : each.value.ip_cidr_range + ) + : each.value.ip_cidr_range + ) allow_subnet_cidr_routes_overlap = each.value.allow_subnet_cidr_routes_overlap description = ( each.value.description == null @@ -154,12 +164,19 @@ resource "google_compute_subnetwork" "subnetwork" { ) private_ip_google_access = each.value.enable_private_access stack_type = ( - try(each.value.ipv6, null) != null ? "IPV4_IPV6" : null + try(each.value.ipv6, null) != null + ? ( + try(each.value.ipv6.ipv6_only, false) + ? "IPV6_ONLY" + : "IPV4_IPV6" + ) + : null ) ipv6_access_type = ( try(each.value.ipv6, null) != null ? each.value.ipv6.access_type : null ) private_ipv6_google_access = try(each.value.ipv6.enable_private_access, null) + ip_collection = each.value.ip_collection send_secondary_ip_range_if_empty = true dynamic "secondary_ip_range" { diff --git a/modules/net-vpc/variables.tf b/modules/net-vpc/variables.tf index 39e6674eb..ebbe9655c 100644 --- a/modules/net-vpc/variables.tf +++ b/modules/net-vpc/variables.tf @@ -274,7 +274,9 @@ variable "subnets" { access_type = optional(string, "INTERNAL") # this field is marked for internal use in the API documentation # enable_private_access = optional(string) + ipv6_only = optional(bool, false) })) + ip_collection = optional(string, null) secondary_ip_ranges = optional(map(string)) iam = optional(map(list(string)), {}) iam_bindings = optional(map(object({ diff --git a/tests/modules/net_vpc/examples/ipv6_only.yaml b/tests/modules/net_vpc/examples/ipv6_only.yaml new file mode 100644 index 000000000..c6001ebd1 --- /dev/null +++ b/tests/modules/net_vpc/examples/ipv6_only.yaml @@ -0,0 +1,82 @@ +# 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: + module.vpc.google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + enable_ula_internal_ipv6: true + # internal_ipv6_range: fd20:6b2:27e5:0:0:0:0:0/48 + name: my-network + network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL + project: project-id + routing_mode: GLOBAL + timeouts: null + module.vpc.google_compute_route.gateway["private-googleapis"]: + description: Terraform-managed. + dest_range: 199.36.153.8/30 + name: my-network-private-googleapis + next_hop_gateway: default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + priority: 1000 + project: project-id + tags: null + timeouts: null + module.vpc.google_compute_route.gateway["restricted-googleapis"]: + description: Terraform-managed. + dest_range: 199.36.153.4/30 + name: my-network-restricted-googleapis + next_hop_gateway: default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + priority: 1000 + project: project-id + tags: null + timeouts: null + module.vpc.google_compute_subnetwork.subnetwork["europe-west1/test-v6only"]: + description: Terraform-managed. + ipv6_access_type: INTERNAL + log_config: [] + name: test-v6only + private_ip_google_access: true + project: project-id + region: europe-west1 + role: null + stack_type: IPV6_ONLY + timeouts: null + module.vpc.google_compute_subnetwork.subnetwork["europe-west3/test-v6only"]: + description: Terraform-managed. + ipv6_access_type: EXTERNAL + log_config: [] + name: test-v6only + private_ip_google_access: true + project: project-id + region: europe-west3 + role: null + stack_type: IPV6_ONLY + timeouts: null + ip_collection: "https://www.googleapis.com/compute/v1/projects/project-id/regions/europe-west3/publicDelegatedPrefixes/test-sub-pdp" + +counts: + google_compute_network: 1 + google_compute_route: 3 + google_compute_subnetwork: 2 + modules: 1 + resources: 6 + +outputs: {}