From 7c920d7d353a1e93d9177e88f6c82b3a79e47db8 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Thu, 16 Oct 2025 07:54:14 +0200 Subject: [PATCH] Add support for context to net-lb-int net-vpc-firewall and net-vpc module (#3419) * net-lb-int * net-vpc-firewall * net-vpc * net-vpc --- modules/net-lb-int/README.md | 25 +-- modules/net-lb-int/groups.tf | 4 +- modules/net-lb-int/health-check.tf | 2 +- modules/net-lb-int/main.tf | 53 ++++-- modules/net-lb-int/variables.tf | 13 ++ modules/net-vpc-firewall/README.md | 15 +- modules/net-vpc-firewall/default-rules.tf | 62 ++++--- modules/net-vpc-firewall/main.tf | 46 +++-- modules/net-vpc-firewall/variables.tf | 12 ++ modules/net-vpc/README.md | 54 +++--- modules/net-vpc/internal-ranges.tf | 19 +- modules/net-vpc/main.tf | 54 ++++-- modules/net-vpc/psa.tf | 5 +- modules/net-vpc/routes.tf | 72 ++++--- modules/net-vpc/subnets.tf | 152 +++++++++------ modules/net-vpc/variables.tf | 15 +- tests/modules/net_lb_int/context.tfvars | 42 +++++ tests/modules/net_lb_int/context.yaml | 126 +++++++++++++ tests/modules/net_lb_int/tftest.yaml | 1 + tests/modules/net_vpc/context.tfvars | 96 ++++++++++ tests/modules/net_vpc/context.yaml | 175 ++++++++++++++++++ tests/modules/net_vpc/tftest.yaml | 1 + tests/modules/net_vpc_firewall/context.tfvars | 47 +++++ tests/modules/net_vpc_firewall/context.yaml | 157 ++++++++++++++++ tests/modules/net_vpc_firewall/tftest.yaml | 17 ++ 25 files changed, 1044 insertions(+), 221 deletions(-) create mode 100644 tests/modules/net_lb_int/context.tfvars create mode 100644 tests/modules/net_lb_int/context.yaml create mode 100644 tests/modules/net_vpc/context.tfvars create mode 100644 tests/modules/net_vpc/context.yaml create mode 100644 tests/modules/net_vpc_firewall/context.tfvars create mode 100644 tests/modules/net_vpc_firewall/context.yaml create mode 100644 tests/modules/net_vpc_firewall/tftest.yaml diff --git a/modules/net-lb-int/README.md b/modules/net-lb-int/README.md index 5ed7b40a9..99ae056fe 100644 --- a/modules/net-lb-int/README.md +++ b/modules/net-lb-int/README.md @@ -374,20 +374,21 @@ One other issue is a `Provider produced inconsistent final plan` error which is | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L188) | Name used for all resources. | string | ✓ | | -| [project_id](variables.tf#L193) | Project id where resources will be created. | string | ✓ | | -| [region](variables.tf#L198) | GCP region. | string | ✓ | | -| [vpc_config](variables.tf#L224) | VPC-level configuration. | object({…}) | ✓ | | +| [name](variables.tf#L201) | Name used for all resources. | string | ✓ | | +| [project_id](variables.tf#L206) | Project id where resources will be created. | string | ✓ | | +| [region](variables.tf#L211) | GCP region. | string | ✓ | | +| [vpc_config](variables.tf#L237) | VPC-level configuration. | object({…}) | ✓ | | | [backend_service_config](variables.tf#L17) | Backend service level configuration. | object({…}) | | {} | | [backends](variables.tf#L53) | Load balancer backends. | list(object({…})) | | [] | -| [description](variables.tf#L64) | Optional description used for resources. | string | | "Terraform managed." | -| [forwarding_rules_config](variables.tf#L70) | The optional forwarding rules configuration. | map(object({…})) | | {…} | -| [group_configs](variables.tf#L86) | Optional unmanaged groups to create. Can be referenced in backends via outputs. | map(object({…})) | | {} | -| [health_check](variables.tf#L98) | Name of existing health check to use, disables auto-created health check. Also set `health_check_config = null` when cross-referencing an health check from another load balancer module to avoid a Terraform error. | string | | null | -| [health_check_config](variables.tf#L104) | Optional auto-created health check configuration, use the output self-link to set it in the auto healing policy. Refer to examples for usage. | object({…}) | | {…} | -| [labels](variables.tf#L182) | Labels set on resources. | map(string) | | {} | -| [service_attachments](variables.tf#L203) | PSC service attachments, keyed by forwarding rule. | map(object({…})) | | null | -| [service_label](variables.tf#L218) | Optional prefix of the fully qualified forwarding rule name. | string | | null | +| [context](variables.tf#L64) | Context-specific interpolations. | object({…}) | | {} | +| [description](variables.tf#L77) | Optional description used for resources. | string | | "Terraform managed." | +| [forwarding_rules_config](variables.tf#L83) | The optional forwarding rules configuration. | map(object({…})) | | {…} | +| [group_configs](variables.tf#L99) | Optional unmanaged groups to create. Can be referenced in backends via outputs. | map(object({…})) | | {} | +| [health_check](variables.tf#L111) | Name of existing health check to use, disables auto-created health check. Also set `health_check_config = null` when cross-referencing an health check from another load balancer module to avoid a Terraform error. | string | | null | +| [health_check_config](variables.tf#L117) | Optional auto-created health check configuration, use the output self-link to set it in the auto healing policy. Refer to examples for usage. | object({…}) | | {…} | +| [labels](variables.tf#L195) | Labels set on resources. | map(string) | | {} | +| [service_attachments](variables.tf#L216) | PSC service attachments, keyed by forwarding rule. | map(object({…})) | | null | +| [service_label](variables.tf#L231) | Optional prefix of the fully qualified forwarding rule name. | string | | null | ## Outputs diff --git a/modules/net-lb-int/groups.tf b/modules/net-lb-int/groups.tf index 736dfc6f5..3492f5b3d 100644 --- a/modules/net-lb-int/groups.tf +++ b/modules/net-lb-int/groups.tf @@ -18,8 +18,8 @@ resource "google_compute_instance_group" "default" { for_each = var.group_configs - project = var.project_id - zone = each.value.zone + project = local.project_id + zone = lookup(local.ctx.locations, each.value.zone, each.value.zone) name = "${var.name}-${each.key}" description = each.value.description instances = each.value.instances diff --git a/modules/net-lb-int/health-check.tf b/modules/net-lb-int/health-check.tf index 1e0bd193b..18bc4ccac 100644 --- a/modules/net-lb-int/health-check.tf +++ b/modules/net-lb-int/health-check.tf @@ -31,7 +31,7 @@ locals { resource "google_compute_health_check" "default" { provider = google-beta count = local.hc != null ? 1 : 0 - project = var.project_id + project = local.project_id name = coalesce(local.hc.name, var.name) description = local.hc.description check_interval_sec = local.hc.check_interval_sec diff --git a/modules/net-lb-int/main.tf b/modules/net-lb-int/main.tf index 201d1e3eb..c517c662b 100644 --- a/modules/net-lb-int/main.tf +++ b/modules/net-lb-int/main.tf @@ -16,19 +16,34 @@ locals { + _service_attachments = ( + var.service_attachments == null ? {} : var.service_attachments + ) bs_conntrack = var.backend_service_config.connection_tracking bs_failover = var.backend_service_config.failover_config forwarding_rule_names = { for k, v in var.forwarding_rules_config : k => k == "" ? var.name : "${var.name}-${k}" } + ctx = { + for k, v in var.context : k => { + for kk, vv in v : "${local.ctx_p}${k}:${kk}" => vv + } + } + ctx_p = "$" health_check = ( var.health_check != null ? var.health_check : google_compute_health_check.default[0].self_link ) - _service_attachments = ( - var.service_attachments == null ? {} : var.service_attachments + network = lookup( + local.ctx.networks, var.vpc_config.network, var.vpc_config.network + ) + project_id = lookup( + local.ctx.project_ids, var.project_id, var.project_id + ) + region = lookup( + local.ctx.locations, var.region, var.region ) service_attachments = { for k, v in local._service_attachments : @@ -44,36 +59,38 @@ moved { resource "google_compute_forwarding_rule" "default" { for_each = var.forwarding_rules_config provider = google-beta - project = var.project_id + project = local.project_id name = coalesce(each.value.name, local.forwarding_rule_names[each.key]) - region = var.region + region = local.region description = each.value.description - ip_address = each.value.address + ip_address = try(local.ctx.addresses[each.value.address], each.value.address) ip_protocol = each.value.protocol ip_version = each.value.address != null ? null : each.value.ipv6 == true ? "IPV6" : "IPV4" # do not set if address is provided backend_service = ( google_compute_region_backend_service.default.self_link ) load_balancing_scheme = "INTERNAL" - network = var.vpc_config.network + network = local.network ports = each.value.ports # "nnnnn" or "nnnnn,nnnnn,nnnnn" max 5 - subnetwork = var.vpc_config.subnetwork - allow_global_access = each.value.global_access - labels = var.labels - all_ports = each.value.ports == null ? true : null - service_label = var.service_label + subnetwork = lookup( + local.ctx.subnets, var.vpc_config.subnetwork, var.vpc_config.subnetwork + ) + allow_global_access = each.value.global_access + labels = var.labels + all_ports = each.value.ports == null ? true : null + service_label = var.service_label # is_mirroring_collector = false } resource "google_compute_region_backend_service" "default" { provider = google-beta - project = var.project_id - region = var.region + project = local.project_id + region = local.region name = coalesce(var.backend_service_config.name, var.name) description = var.backend_service_config.description load_balancing_scheme = "INTERNAL" protocol = var.backend_service_config.protocol - network = var.vpc_config.network + network = local.network health_checks = [local.health_check] connection_draining_timeout_sec = var.backend_service_config.connection_draining_timeout_sec session_affinity = var.backend_service_config.session_affinity @@ -135,12 +152,14 @@ resource "google_compute_region_backend_service" "default" { resource "google_compute_service_attachment" "default" { for_each = local.service_attachments - project = var.project_id - region = var.region + project = local.project_id + region = local.region name = local.forwarding_rule_names[each.key] description = var.description target_service = google_compute_forwarding_rule.default[each.key].id - nat_subnets = each.value.nat_subnets + nat_subnets = each.value.nat_subnets == null ? null : [ + for s in each.value.nat_subnets : lookup(local.ctx.subnets, s, s) + ] connection_preference = ( each.value.automatic_connection ? "ACCEPT_AUTOMATIC" : "ACCEPT_MANUAL" ) diff --git a/modules/net-lb-int/variables.tf b/modules/net-lb-int/variables.tf index 5df659c0a..5b023581f 100644 --- a/modules/net-lb-int/variables.tf +++ b/modules/net-lb-int/variables.tf @@ -61,6 +61,19 @@ variable "backends" { nullable = false } +variable "context" { + description = "Context-specific interpolations." + type = object({ + addresses = optional(map(string), {}) + locations = optional(map(string), {}) + networks = optional(map(string), {}) + project_ids = optional(map(string), {}) + subnets = optional(map(string), {}) + }) + default = {} + nullable = false +} + variable "description" { description = "Optional description used for resources." type = string diff --git a/modules/net-vpc-firewall/README.md b/modules/net-vpc-firewall/README.md index 8750d073f..f074556e8 100644 --- a/modules/net-vpc-firewall/README.md +++ b/modules/net-vpc-firewall/README.md @@ -269,13 +269,14 @@ module "firewall" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [network](variables.tf#L111) | Name of the network this set of firewall rules applies to. | string | ✓ | | -| [project_id](variables.tf#L116) | Project id of the project that holds the network. | string | ✓ | | -| [default_rules_config](variables.tf#L17) | Optionally created convenience rules. Set the 'disabled' attribute to true, or individual rule attributes to empty lists to disable. | object({…}) | | {} | -| [egress_rules](variables.tf#L37) | List of egress rule definitions, default to deny action. Null destination ranges will be replaced with 0/0. | map(object({…})) | | {} | -| [factories_config](variables.tf#L60) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | -| [ingress_rules](variables.tf#L70) | List of ingress rule definitions, default to allow action. Null source ranges will be replaced with 0/0. | map(object({…})) | | {} | -| [named_ranges](variables.tf#L94) | Define mapping of names to ranges that can be used in custom rules. | map(list(string)) | | {…} | +| [network](variables.tf#L123) | Name of the network this set of firewall rules applies to. | string | ✓ | | +| [project_id](variables.tf#L128) | Project id of the project that holds the network. | string | ✓ | | +| [context](variables.tf#L17) | Context-specific interpolations. | object({…}) | | {} | +| [default_rules_config](variables.tf#L29) | Optionally created convenience rules. Set the 'disabled' attribute to true, or individual rule attributes to empty lists to disable. | object({…}) | | {} | +| [egress_rules](variables.tf#L49) | List of egress rule definitions, default to deny action. Null destination ranges will be replaced with 0/0. | map(object({…})) | | {} | +| [factories_config](variables.tf#L72) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | +| [ingress_rules](variables.tf#L82) | List of ingress rule definitions, default to allow action. Null source ranges will be replaced with 0/0. | map(object({…})) | | {} | +| [named_ranges](variables.tf#L106) | Define mapping of names to ranges that can be used in custom rules. | map(list(string)) | | {…} | ## Outputs diff --git a/modules/net-vpc-firewall/default-rules.tf b/modules/net-vpc-firewall/default-rules.tf index bbca6dd57..a0f38ab70 100644 --- a/modules/net-vpc-firewall/default-rules.tf +++ b/modules/net-vpc-firewall/default-rules.tf @@ -25,23 +25,27 @@ locals { } resource "google_compute_firewall" "allow-admins" { - count = length(local.default_rules.admin_ranges) > 0 ? 1 : 0 - name = "${var.network}-ingress-admins" - description = "Access from the admin subnet to all subnets." - network = var.network - project = var.project_id - source_ranges = local.default_rules.admin_ranges + count = length(local.default_rules.admin_ranges) > 0 ? 1 : 0 + project = local.project_id + network = local.network + name = "${local.network_name}-ingress-admins" + description = "Access from the admin subnet to all subnets." + source_ranges = [ + for r in local.default_rules.admin_ranges : lookup(local.ctx.cidr_ranges, r, r) + ] allow { protocol = "all" } } resource "google_compute_firewall" "allow-tag-http" { - count = length(local.default_rules.http_ranges) > 0 ? 1 : 0 - name = "${var.network}-ingress-tag-http" - description = "Allow http to machines with matching tags." - network = var.network - project = var.project_id - source_ranges = local.default_rules.http_ranges - target_tags = local.default_rules.http_tags + count = length(local.default_rules.http_ranges) > 0 ? 1 : 0 + project = local.project_id + network = local.network + name = "${local.network_name}-ingress-tag-http" + description = "Allow http to machines with matching tags." + source_ranges = [ + for r in local.default_rules.http_ranges : lookup(local.ctx.cidr_ranges, r, r) + ] + target_tags = local.default_rules.http_tags allow { protocol = "tcp" ports = ["80"] @@ -49,13 +53,15 @@ resource "google_compute_firewall" "allow-tag-http" { } resource "google_compute_firewall" "allow-tag-https" { - count = length(local.default_rules.https_ranges) > 0 ? 1 : 0 - name = "${var.network}-ingress-tag-https" - description = "Allow http to machines with matching tags." - network = var.network - project = var.project_id - source_ranges = local.default_rules.https_ranges - target_tags = local.default_rules.https_tags + count = length(local.default_rules.https_ranges) > 0 ? 1 : 0 + project = local.project_id + network = local.network + name = "${local.network_name}-ingress-tag-https" + description = "Allow http to machines with matching tags." + source_ranges = [ + for r in local.default_rules.https_ranges : lookup(local.ctx.cidr_ranges, r, r) + ] + target_tags = local.default_rules.https_tags allow { protocol = "tcp" ports = ["443"] @@ -63,13 +69,15 @@ resource "google_compute_firewall" "allow-tag-https" { } resource "google_compute_firewall" "allow-tag-ssh" { - count = length(local.default_rules.ssh_ranges) > 0 ? 1 : 0 - name = "${var.network}-ingress-tag-ssh" - description = "Allow SSH to machines with matching tags." - network = var.network - project = var.project_id - source_ranges = local.default_rules.ssh_ranges - target_tags = local.default_rules.ssh_tags + count = length(local.default_rules.ssh_ranges) > 0 ? 1 : 0 + project = local.project_id + network = local.network + name = "${local.network_name}-ingress-tag-ssh" + description = "Allow SSH to machines with matching tags." + source_ranges = [ + for r in local.default_rules.ssh_ranges : lookup(local.ctx.cidr_ranges, r, r) + ] + target_tags = local.default_rules.ssh_tags allow { protocol = "tcp" ports = ["22"] diff --git a/modules/net-vpc-firewall/main.tf b/modules/net-vpc-firewall/main.tf index 5fed5817e..4a55de8ed 100644 --- a/modules/net-vpc-firewall/main.tf +++ b/modules/net-vpc-firewall/main.tf @@ -66,6 +66,15 @@ locals { for name, rule in merge(var.ingress_rules) : name => merge(rule, { direction = "INGRESS" }) } + ctx = { + for k, v in var.context : k => { + for kk, vv in v : "${local.ctx_p}${k}:${kk}" => vv + } + } + ctx_p = "$" + network = lookup(local.ctx.networks, var.network, var.network) + network_name = reverse(split("/", local.network))[0] + project_id = lookup(local.ctx.project_ids, var.project_id, var.project_id) # convert rules data to resource format and replace range template variables rules = { for name, rule in local._rules : @@ -94,30 +103,32 @@ locals { resource "google_compute_firewall" "custom-rules" { for_each = local.rules - project = var.project_id - network = var.network + project = local.project_id + network = local.network name = each.key description = each.value.description direction = each.value.direction source_ranges = ( - each.value.direction == "INGRESS" + each.value.source_ranges == null ? ( - each.value.source_ranges == null && each.value.sources == null + each.value.direction == "INGRESS" && each.value.sources == null ? ["0.0.0.0/0"] - : each.value.source_ranges + : null ) - #for egress, we will include the source_ranges when provided. Previously, null was forced - : each.value.source_ranges + : [ + for r in each.value.source_ranges : lookup(local.ctx.cidr_ranges, r, r) + ] ) destination_ranges = ( - each.value.direction == "EGRESS" + each.value.destination_ranges == null ? ( - each.value.destination_ranges == null + each.value.direction == "EGRESS" ? ["0.0.0.0/0"] - : each.value.destination_ranges + : null ) - #for ingress, we will include the destination_ranges when provided. Previously, null was forced - : each.value.destination_ranges + : [ + for r in each.value.destination_ranges : lookup(local.ctx.cidr_ranges, r, r) + ] ) source_tags = ( each.value.use_service_accounts || each.value.direction == "EGRESS" @@ -126,14 +137,19 @@ resource "google_compute_firewall" "custom-rules" { ) source_service_accounts = ( each.value.use_service_accounts && each.value.direction == "INGRESS" - ? each.value.sources + ? (each.value.sources == null ? null : [ + for s in each.value.sources : lookup(local.ctx.iam_principals, s, s) + ]) : null ) target_tags = ( - each.value.use_service_accounts ? null : each.value.targets + !each.value.use_service_accounts ? each.value.targets : null ) target_service_accounts = ( - each.value.use_service_accounts ? each.value.targets : null + !each.value.use_service_accounts ? null : ( + each.value.targets == null ? null : [ + for s in each.value.targets : lookup(local.ctx.iam_principals, s, s) + ]) ) disabled = each.value.disabled == true priority = each.value.priority diff --git a/modules/net-vpc-firewall/variables.tf b/modules/net-vpc-firewall/variables.tf index 104f87d5b..594492694 100644 --- a/modules/net-vpc-firewall/variables.tf +++ b/modules/net-vpc-firewall/variables.tf @@ -14,6 +14,18 @@ * limitations under the License. */ +variable "context" { + description = "Context-specific interpolations." + type = object({ + cidr_ranges = optional(map(string), {}) + iam_principals = optional(map(string), {}) + networks = optional(map(string), {}) + project_ids = optional(map(string), {}) + }) + default = {} + nullable = false +} + variable "default_rules_config" { description = "Optionally created convenience rules. Set the 'disabled' attribute to true, or individual rule attributes to empty lists to disable." type = object({ diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 8309ec6c4..af95ac727 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -486,7 +486,7 @@ module "vpc" { project_id = var.project_id name = "my-network" context = { - regions = { + locations = { primary = "europe-west4" secondary = "europe-west8" } @@ -500,7 +500,7 @@ module "vpc" { ```yaml name: simple -region: $regions:primary +region: $locations:primary ip_cidr_range: 10.0.1.0/24 # tftest-file id=subnet-simple path=config/subnets/subnet-simple.yaml schema=subnet.schema.json @@ -899,32 +899,32 @@ secondary_ip_ranges: | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L171) | The name of the network being created. | string | ✓ | | -| [project_id](variables.tf#L248) | The ID of the project where this VPC will be created. | string | ✓ | | +| [name](variables.tf#L184) | The name of the network being created. | string | ✓ | | +| [project_id](variables.tf#L261) | The ID of the project where this VPC will be created. | string | ✓ | | | [auto_create_subnetworks](variables.tf#L17) | Set to true to create an auto mode subnet, defaults to custom mode. | bool | | false | -| [context](variables.tf#L23) | Context-specific interpolations. | object({…}) | | {} | -| [create_googleapis_routes](variables.tf#L32) | Toggle creation of googleapis private/restricted routes. Disabled when vpc creation is turned off, or when set to null. | object({…}) | | {} | -| [delete_default_routes_on_create](variables.tf#L45) | Set to true to delete the default routes at creation time. | bool | | false | -| [description](variables.tf#L51) | An optional description of this resource (triggers recreation on change). | string | | "Terraform-managed." | -| [dns_policy](variables.tf#L57) | DNS policy setup for the VPC. | object({…}) | | null | -| [factories_config](variables.tf#L70) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | -| [firewall_policy_enforcement_order](variables.tf#L79) | Order that Firewall Rules and Firewall Policies are evaluated. Can be either 'BEFORE_CLASSIC_FIREWALL' or 'AFTER_CLASSIC_FIREWALL'. | string | | "AFTER_CLASSIC_FIREWALL" | -| [internal_ranges](variables.tf#L91) | Internal range configuration for IPAM operations within the VPC network. | list(object({…})) | | [] | -| [ipv6_config](variables.tf#L155) | Optional IPv6 configuration for this network. | object({…}) | | {} | -| [mtu](variables.tf#L165) | Maximum Transmission Unit in bytes. The minimum value for this field is 1460 (the default) and the maximum value is 1500 bytes. | number | | null | -| [network_attachments](variables.tf#L176) | PSC network attachments, names as keys. | map(object({…})) | | {} | -| [peering_config](variables.tf#L189) | VPC peering configuration. | object({…}) | | null | -| [policy_based_routes](variables.tf#L200) | Policy based routes, keyed by name. | map(object({…})) | | {} | -| [psa_configs](variables.tf#L253) | The Private Service Access configuration. | list(object({…})) | | [] | -| [routes](variables.tf#L284) | Network routes, keyed by name. | map(object({…})) | | {} | -| [routing_mode](variables.tf#L305) | The network routing mode (default 'GLOBAL'). | string | | "GLOBAL" | -| [shared_vpc_host](variables.tf#L315) | Enable shared VPC for this project. | bool | | false | -| [shared_vpc_service_projects](variables.tf#L321) | Shared VPC service projects to register with this host. | list(string) | | [] | -| [subnets](variables.tf#L327) | Subnet configuration. | list(object({…})) | | [] | -| [subnets_private_nat](variables.tf#L407) | List of private NAT subnets. | list(object({…})) | | [] | -| [subnets_proxy_only](variables.tf#L419) | 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#L453) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | -| [vpc_reuse](variables.tf#L485) | Reuse existing VPC if not null. If the network_id number is not passed in, a data source is used. | object({…}) | | null | +| [context](variables.tf#L23) | Context-specific interpolations. | object({…}) | | {} | +| [create_googleapis_routes](variables.tf#L45) | Toggle creation of googleapis private/restricted routes. Disabled when vpc creation is turned off, or when set to null. | object({…}) | | {} | +| [delete_default_routes_on_create](variables.tf#L58) | Set to true to delete the default routes at creation time. | bool | | false | +| [description](variables.tf#L64) | An optional description of this resource (triggers recreation on change). | string | | "Terraform-managed." | +| [dns_policy](variables.tf#L70) | DNS policy setup for the VPC. | object({…}) | | null | +| [factories_config](variables.tf#L83) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | +| [firewall_policy_enforcement_order](variables.tf#L92) | Order that Firewall Rules and Firewall Policies are evaluated. Can be either 'BEFORE_CLASSIC_FIREWALL' or 'AFTER_CLASSIC_FIREWALL'. | string | | "AFTER_CLASSIC_FIREWALL" | +| [internal_ranges](variables.tf#L104) | Internal range configuration for IPAM operations within the VPC network. | list(object({…})) | | [] | +| [ipv6_config](variables.tf#L168) | Optional IPv6 configuration for this network. | object({…}) | | {} | +| [mtu](variables.tf#L178) | Maximum Transmission Unit in bytes. The minimum value for this field is 1460 (the default) and the maximum value is 1500 bytes. | number | | null | +| [network_attachments](variables.tf#L189) | PSC network attachments, names as keys. | map(object({…})) | | {} | +| [peering_config](variables.tf#L202) | VPC peering configuration. | object({…}) | | null | +| [policy_based_routes](variables.tf#L213) | Policy based routes, keyed by name. | map(object({…})) | | {} | +| [psa_configs](variables.tf#L266) | The Private Service Access configuration. | list(object({…})) | | [] | +| [routes](variables.tf#L297) | Network routes, keyed by name. | map(object({…})) | | {} | +| [routing_mode](variables.tf#L318) | The network routing mode (default 'GLOBAL'). | string | | "GLOBAL" | +| [shared_vpc_host](variables.tf#L328) | Enable shared VPC for this project. | bool | | false | +| [shared_vpc_service_projects](variables.tf#L334) | Shared VPC service projects to register with this host. | list(string) | | [] | +| [subnets](variables.tf#L340) | Subnet configuration. | list(object({…})) | | [] | +| [subnets_private_nat](variables.tf#L420) | List of private NAT subnets. | list(object({…})) | | [] | +| [subnets_proxy_only](variables.tf#L432) | 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#L466) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | +| [vpc_reuse](variables.tf#L498) | Reuse existing VPC if not null. If the network_id number is not passed in, a data source is used. | object({…}) | | null | ## Outputs diff --git a/modules/net-vpc/internal-ranges.tf b/modules/net-vpc/internal-ranges.tf index 2304919d4..0a78b6717 100644 --- a/modules/net-vpc/internal-ranges.tf +++ b/modules/net-vpc/internal-ranges.tf @@ -52,12 +52,10 @@ locals { } } } - internal_ranges = merge( { for r in var.internal_ranges : r.name => r }, local._factory_internal_ranges ) - internal_ranges_ids = { for k, v in google_network_connectivity_internal_range.internal_range : k => v.id @@ -65,14 +63,15 @@ locals { } resource "google_network_connectivity_internal_range" "internal_range" { - provider = google-beta - for_each = local.internal_ranges - project = var.project_id - name = each.value.name - network = local.network.id - - description = each.value.description - ip_cidr_range = each.value.ip_cidr_range + provider = google-beta + for_each = local.internal_ranges + project = local.project_id + name = each.value.name + network = local.network.id + description = each.value.description + ip_cidr_range = try( + local.ctx.cidr_ranges[each.value.ip_cidr_range], each.value.ip_cidr_range + ) labels = each.value.labels usage = each.value.usage peering = each.value.peering diff --git a/modules/net-vpc/main.tf b/modules/net-vpc/main.tf index 6fa23edd9..446505cb5 100644 --- a/modules/net-vpc/main.tf +++ b/modules/net-vpc/main.tf @@ -15,11 +15,14 @@ */ locals { - ctx = { + _ctx = { for k, v in var.context : k => { for kk, vv in v : "${local.ctx_p}${k}:${kk}" => vv } } + ctx = merge(local._ctx, { + locations = merge(local._ctx.regions, local._ctx.locations) + }) ctx_p = "$" network = ( var.vpc_reuse == null @@ -56,19 +59,28 @@ locals { peer_network = ( var.peering_config == null ? null - : element(reverse(split("/", var.peering_config.peer_vpc_self_link)), 0) + : ( + startswith(var.peering_config.peer_vpc_self_link, "$networks:") + ? lookup( + local.ctx.networks, + var.peering_config.peer_vpc_self_link, + var.peering_config.peer_vpc_self_link + ) + : element(reverse(split("/", var.peering_config.peer_vpc_self_link)), 0) + ) ) + project_id = lookup(local.ctx.project_ids, var.project_id, var.project_id) } data "google_compute_network" "network" { count = try(var.vpc_reuse.use_data_source, null) == true ? 1 : 0 name = var.name - project = var.project_id + project = local.project_id } resource "google_compute_network" "network" { count = var.vpc_reuse == null ? 1 : 0 - project = var.project_id + project = local.project_id name = var.name description = var.description auto_create_subnetworks = var.auto_create_subnetworks @@ -81,11 +93,15 @@ resource "google_compute_network" "network" { } resource "google_compute_network_peering" "local" { - provider = google-beta - count = var.peering_config == null ? 0 : 1 - name = "${var.name}-${local.peer_network}" - network = local.network.self_link - peer_network = var.peering_config.peer_vpc_self_link + provider = google-beta + count = var.peering_config == null ? 0 : 1 + name = "${var.name}-${local.peer_network}" + network = local.network.self_link + peer_network = lookup( + local.ctx.networks, + var.peering_config.peer_vpc_self_link, + var.peering_config.peer_vpc_self_link + ) export_custom_routes = var.peering_config.export_routes import_custom_routes = var.peering_config.import_routes } @@ -97,8 +113,12 @@ resource "google_compute_network_peering" "remote" { ? 1 : 0 ) - name = "${local.peer_network}-${var.name}" - network = var.peering_config.peer_vpc_self_link + name = "${local.peer_network}-${var.name}" + network = lookup( + local.ctx.networks, + var.peering_config.peer_vpc_self_link, + var.peering_config.peer_vpc_self_link + ) peer_network = local.network.self_link export_custom_routes = var.peering_config.import_routes import_custom_routes = var.peering_config.export_routes @@ -108,7 +128,7 @@ resource "google_compute_network_peering" "remote" { resource "google_compute_shared_vpc_host_project" "shared_vpc_host" { provider = google-beta count = var.shared_vpc_host ? 1 : 0 - project = var.project_id + project = local.project_id depends_on = [local.network] } @@ -119,14 +139,14 @@ resource "google_compute_shared_vpc_service_project" "service_projects" { ? var.shared_vpc_service_projects : [] ) - host_project = var.project_id - service_project = each.value + host_project = local.project_id + service_project = lookup(local.ctx.project_ids, each.value, each.value) depends_on = [google_compute_shared_vpc_host_project.shared_vpc_host] } resource "google_dns_policy" "default" { count = var.dns_policy == null ? 0 : 1 - project = var.project_id + project = local.project_id name = var.name enable_inbound_forwarding = try(var.dns_policy.inbound, null) enable_logging = try(var.dns_policy.logging, null) @@ -145,7 +165,7 @@ resource "google_dns_policy" "default" { ) iterator = ns content { - ipv4_address = ns.value + ipv4_address = lookup(local.ctx.addresses, ns.value, ns.value) forwarding_path = "private" } } @@ -157,7 +177,7 @@ resource "google_dns_policy" "default" { ) iterator = ns content { - ipv4_address = ns.value + ipv4_address = lookup(local.ctx.addresses, ns.value, ns.value) } } } diff --git a/modules/net-vpc/psa.tf b/modules/net-vpc/psa.tf index 959193896..8b9855896 100644 --- a/modules/net-vpc/psa.tf +++ b/modules/net-vpc/psa.tf @@ -44,7 +44,8 @@ locals { }) } psa_configs_ranges = { - for v in local._psa_configs_ranges : v.key => v.value + for v in local._psa_configs_ranges : + v.key => lookup(local.ctx.cidr_ranges, v.value, v.value) } psa_peered_domains = { for v in local._psa_peered_domains : v.key => v @@ -53,7 +54,7 @@ locals { resource "google_compute_global_address" "psa_ranges" { for_each = local.psa_configs_ranges - project = var.project_id + project = local.project_id network = local.network.id name = each.key purpose = "VPC_PEERING" diff --git a/modules/net-vpc/routes.tf b/modules/net-vpc/routes.tf index e8fd8812b..c31440aee 100644 --- a/modules/net-vpc/routes.tf +++ b/modules/net-vpc/routes.tf @@ -50,36 +50,42 @@ locals { } resource "google_compute_route" "gateway" { - for_each = local.routes.gateway - project = var.project_id - network = local.network.name - name = "${var.name}-${each.key}" - description = each.value.description - dest_range = each.value.dest_range + for_each = local.routes.gateway + project = local.project_id + network = local.network.name + name = "${var.name}-${each.key}" + description = each.value.description + dest_range = lookup( + local.ctx.cidr_ranges, each.value.dest_range, each.value.dest_range + ) priority = each.value.priority tags = each.value.tags next_hop_gateway = each.value.next_hop } resource "google_compute_route" "ilb" { - for_each = local.routes.ilb - project = var.project_id - network = local.network.name - name = "${var.name}-${each.key}" - description = each.value.description - dest_range = each.value.dest_range + for_each = local.routes.ilb + project = local.project_id + network = local.network.name + name = "${var.name}-${each.key}" + description = each.value.description + dest_range = lookup( + local.ctx.cidr_ranges, each.value.dest_range, each.value.dest_range + ) priority = each.value.priority tags = each.value.tags next_hop_ilb = each.value.next_hop } resource "google_compute_route" "instance" { - for_each = local.routes.instance - project = var.project_id - network = local.network.name - name = "${var.name}-${each.key}" - description = each.value.description - dest_range = each.value.dest_range + for_each = local.routes.instance + project = local.project_id + network = local.network.name + name = "${var.name}-${each.key}" + description = each.value.description + dest_range = lookup( + local.ctx.cidr_ranges, each.value.dest_range, each.value.dest_range + ) priority = each.value.priority tags = each.value.tags next_hop_instance = each.value.next_hop @@ -89,23 +95,29 @@ resource "google_compute_route" "instance" { resource "google_compute_route" "ip" { for_each = local.routes.ip - project = var.project_id + project = local.project_id network = local.network.name name = "${var.name}-${each.key}" description = each.value.description - dest_range = each.value.dest_range - priority = each.value.priority - tags = each.value.tags - next_hop_ip = each.value.next_hop + dest_range = lookup( + local.ctx.cidr_ranges, each.value.dest_range, each.value.dest_range + ) + priority = each.value.priority + tags = each.value.tags + next_hop_ip = lookup( + local.ctx.addresses, each.value.next_hop, each.value.next_hop + ) } resource "google_compute_route" "vpn_tunnel" { - for_each = local.routes.vpn_tunnel - project = var.project_id - network = local.network.name - name = "${var.name}-${each.key}" - description = each.value.description - dest_range = each.value.dest_range + for_each = local.routes.vpn_tunnel + project = local.project_id + network = local.network.name + name = "${var.name}-${each.key}" + description = each.value.description + dest_range = lookup( + local.ctx.cidr_ranges, each.value.dest_range, each.value.dest_range + ) priority = each.value.priority tags = each.value.tags next_hop_vpn_tunnel = each.value.next_hop @@ -113,7 +125,7 @@ resource "google_compute_route" "vpn_tunnel" { resource "google_network_connectivity_policy_based_route" "default" { for_each = var.policy_based_routes - project = var.project_id + project = local.project_id network = local.network.id name = "${var.name}-${each.key}" description = each.value.description diff --git a/modules/net-vpc/subnets.tf b/modules/net-vpc/subnets.tf index 5d3ff70f0..6d6f5891e 100644 --- a/modules/net-vpc/subnets.tf +++ b/modules/net-vpc/subnets.tf @@ -23,7 +23,7 @@ locals { } _factory_data = { for k, v in local._factory_data_raw : k => merge(v, { - region_computed = lookup(local.ctx.regions, v.region, v.region) + region_computed = lookup(local.ctx.locations, v.region, v.region) }) } _factory_path = try(pathexpand(var.factories_config.subnets_folder), null) @@ -88,7 +88,9 @@ locals { _is_proxy_only = try(v.proxy_only == true, false) } } - + _iam_subnets = concat( + var.subnets, var.subnets_psc, var.subnets_proxy_only, values(local._factory_subnets) + ) all_subnets = merge( { for k, v in google_compute_subnetwork.subnetwork : k => v }, { for k, v in google_compute_subnetwork.proxy_only : k => v }, @@ -96,22 +98,21 @@ locals { ) subnet_iam = flatten(concat( [ - for s in concat(var.subnets, var.subnets_psc, var.subnets_proxy_only, values(local._factory_subnets)) : [ - for role, members in s.iam : - { - role = role + for s in local._iam_subnets : [ + for role, members in s.iam : { + role = lookup(local.ctx.custom_roles, role, role) members = members - subnet = "${s.region}/${s.name}" + subnet = "${lookup(local.ctx.locations, s.region, s.region)}/${s.name}" } ] ], )) subnet_iam_bindings = merge([ - for s in concat(var.subnets, var.subnets_psc, var.subnets_proxy_only, values(local._factory_subnets)) : { + for s in local._iam_subnets : { for key, data in s.iam_bindings : key => { - role = data.role - subnet = "${s.region}/${s.name}" + role = lookup(local.ctx.custom_roles, data.role, data.role) + subnet = "${lookup(local.ctx.locations, s.region, s.region)}/${s.name}" members = data.members condition = data.condition } @@ -121,42 +122,60 @@ locals { # In other words, if you have multiple additive bindings with the # same name, only one will be used subnet_iam_bindings_additive = merge([ - for s in concat(var.subnets, var.subnets_psc, var.subnets_proxy_only, values(local._factory_subnets)) : { + for s in local._iam_subnets : { for key, data in s.iam_bindings_additive : key => { - role = data.role - subnet = "${s.region}/${s.name}" + role = lookup(local.ctx.custom_roles, data.role, data.role) + subnet = "${lookup(local.ctx.locations, s.region, s.region)}/${s.name}" member = data.member condition = data.condition } } ]...) subnets = merge( - { for s in var.subnets : "${s.region}/${s.name}" => s }, + { + for s in var.subnets : + "${lookup(local.ctx.locations, s.region, s.region)}/${s.name}" => s + }, { for k, v in local._factory_subnets : k => v if v._is_regular } ) subnets_proxy_only = merge( - { for s in var.subnets_proxy_only : "${s.region}/${s.name}" => s }, + { + for s in var.subnets_proxy_only : + "${lookup(local.ctx.locations, s.region, s.region)}/${s.name}" => s + }, { for k, v in local._factory_subnets : k => v if v._is_proxy_only }, ) subnets_private_nat = merge( - { for s in var.subnets_private_nat : "${s.region}/${s.name}" => s }, + { + for s in var.subnets_private_nat : + "${lookup(local.ctx.locations, s.region, s.region)}/${s.name}" => s + }, # { for k, v in local._factory_subnets : k => v if v._is_proxy_only }, ) subnets_psc = merge( - { for s in var.subnets_psc : "${s.region}/${s.name}" => s }, + { + for s in var.subnets_psc : + "${lookup(local.ctx.locations, s.region, s.region)}/${s.name}" => s + }, { for k, v in local._factory_subnets : k => v if v._is_psc } ) } 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 = try(each.value.ipv6.ipv6_only, false) ? null : each.value.ip_cidr_range + provider = google-beta + for_each = local.subnets + project = local.project_id + network = local.network.name + name = each.value.name + region = lookup( + local.ctx.locations, each.value.region, each.value.region + ) + ip_cidr_range = ( + try(each.value.ipv6.ipv6_only, false) + ? null + : try(local.ctx.cidr_ranges[each.value.ip_cidr_range], each.value.ip_cidr_range) + ) allow_subnet_cidr_routes_overlap = each.value.allow_subnet_cidr_routes_overlap reserved_internal_range = ( each.value.reserved_internal_range != null @@ -189,8 +208,13 @@ resource "google_compute_subnetwork" "subnetwork" { dynamic "secondary_ip_range" { for_each = each.value.secondary_ip_ranges == null ? {} : each.value.secondary_ip_ranges content { - range_name = secondary_ip_range.key - ip_cidr_range = try(secondary_ip_range.value.ip_cidr_range, secondary_ip_range.value) + range_name = secondary_ip_range.key + ip_cidr_range = try( + local.ctx.cidr_ranges[secondary_ip_range.value.ip_cidr_range], + secondary_ip_range.value.ip_cidr_range, + local.ctx.cidr_ranges[secondary_ip_range.value], + secondary_ip_range.value + ) reserved_internal_range = ( try(secondary_ip_range.value.reserved_internal_range, null) != null ? "networkconnectivity.googleapis.com/${try(local.internal_ranges_ids[secondary_ip_range.value.reserved_internal_range], secondary_ip_range.value.reserved_internal_range)}" @@ -215,12 +239,16 @@ resource "google_compute_subnetwork" "subnetwork" { } resource "google_compute_subnetwork" "proxy_only" { - for_each = local.subnets_proxy_only - project = var.project_id - network = local.network.name - name = each.value.name - region = each.value.region - ip_cidr_range = each.value.ip_cidr_range + for_each = local.subnets_proxy_only + project = local.project_id + network = local.network.name + name = each.value.name + region = lookup( + local.ctx.locations, each.value.region, each.value.region + ) + ip_cidr_range = lookup( + local.ctx.cidr_ranges, each.value.ip_cidr_range, each.value.ip_cidr_range + ) description = ( # Set description to an empty string (eg "") to create subnet without a description. each.value.description == null @@ -232,12 +260,16 @@ resource "google_compute_subnetwork" "proxy_only" { } resource "google_compute_subnetwork" "private_nat" { - for_each = local.subnets_private_nat - project = var.project_id - network = local.network.name - name = each.value.name - region = each.value.region - ip_cidr_range = each.value.ip_cidr_range + for_each = local.subnets_private_nat + project = local.project_id + network = local.network.name + name = each.value.name + region = lookup( + local.ctx.locations, each.value.region, each.value.region + ) + ip_cidr_range = lookup( + local.ctx.cidr_ranges, each.value.ip_cidr_range, each.value.ip_cidr_range + ) description = ( # Set description to an empty string (eg "") to create subnet without a description. each.value.description == null @@ -248,12 +280,16 @@ resource "google_compute_subnetwork" "private_nat" { } resource "google_compute_subnetwork" "psc" { - for_each = local.subnets_psc - project = var.project_id - network = local.network.name - name = each.value.name - region = each.value.region - ip_cidr_range = each.value.ip_cidr_range + for_each = local.subnets_psc + project = local.project_id + network = local.network.name + name = each.value.name + region = lookup( + local.ctx.locations, each.value.region, each.value.region + ) + ip_cidr_range = lookup( + local.ctx.cidr_ranges, each.value.ip_cidr_range, each.value.ip_cidr_range + ) description = ( # Set description to an empty string (eg "") to create subnet without a description. each.value.description == null @@ -269,24 +305,30 @@ resource "google_compute_subnetwork_iam_binding" "authoritative" { for binding in local.subnet_iam : "${binding.subnet}.${binding.role}" => binding } - project = var.project_id + project = local.project_id subnetwork = local.all_subnets[each.value.subnet].name region = local.all_subnets[each.value.subnet].region role = each.value.role - members = each.value.members + members = [ + for m in each.value.members : lookup(local.ctx.iam_principals, m, m) + ] } resource "google_compute_subnetwork_iam_binding" "bindings" { for_each = local.subnet_iam_bindings - project = var.project_id + project = local.project_id subnetwork = local.all_subnets[each.value.subnet].name region = local.all_subnets[each.value.subnet].region role = each.value.role - members = each.value.members + members = [ + for m in each.value.members : lookup(local.ctx.iam_principals, m, m) + ] dynamic "condition" { for_each = each.value.condition == null ? [] : [""] content { - expression = each.value.condition.expression + expression = templatestring( + each.value.condition.expression, var.context.condition_vars + ) title = each.value.condition.title description = each.value.condition.description } @@ -295,15 +337,19 @@ resource "google_compute_subnetwork_iam_binding" "bindings" { resource "google_compute_subnetwork_iam_member" "bindings" { for_each = local.subnet_iam_bindings_additive - project = var.project_id + project = local.project_id subnetwork = local.all_subnets[each.value.subnet].name region = local.all_subnets[each.value.subnet].region role = each.value.role - member = each.value.member + member = lookup( + local.ctx.iam_principals, each.value.member, each.value.member + ) dynamic "condition" { for_each = each.value.condition == null ? [] : [""] content { - expression = each.value.condition.expression + expression = templatestring( + each.value.condition.expression, var.context.condition_vars + ) title = each.value.condition.title description = each.value.condition.description } @@ -313,7 +359,7 @@ resource "google_compute_subnetwork_iam_member" "bindings" { resource "google_compute_network_attachment" "default" { provider = google-beta for_each = var.network_attachments - project = var.project_id + project = local.project_id region = google_compute_subnetwork.subnetwork[each.value.subnet].region name = each.key description = each.value.description diff --git a/modules/net-vpc/variables.tf b/modules/net-vpc/variables.tf index 646a75212..1f7c2e02f 100644 --- a/modules/net-vpc/variables.tf +++ b/modules/net-vpc/variables.tf @@ -23,10 +23,23 @@ variable "auto_create_subnetworks" { variable "context" { description = "Context-specific interpolations." type = object({ - regions = optional(map(string), {}) + addresses = optional(map(string), {}) + cidr_ranges = optional(map(string), {}) + condition_vars = optional(map(map(string)), {}) + custom_roles = optional(map(string), {}) + iam_principals = optional(map(string), {}) + locations = optional(map(string), {}) + networks = optional(map(string), {}) + # legacy context + regions = optional(map(string), {}) + project_ids = optional(map(string), {}) }) default = {} nullable = false + validation { + condition = length(var.context.regions) == 0 || length(var.context.locations) == 0 + error_message = "Only one of locations, regions can be used." + } } variable "create_googleapis_routes" { diff --git a/tests/modules/net_lb_int/context.tfvars b/tests/modules/net_lb_int/context.tfvars new file mode 100644 index 000000000..c2aff4baf --- /dev/null +++ b/tests/modules/net_lb_int/context.tfvars @@ -0,0 +1,42 @@ +context = { + addresses = { + test = "10.0.0.10" + } + locations = { + ew8 = "europe-west8" + } + networks = { + test = "projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0" + } + project_ids = { + test = "foo-test-0" + } + subnets = { + test = "projects/foo-dev-net-spoke-0/regions/europe-west8/subnetworks/gce" + test-nat = "projects/foo-dev-net-spoke-0/regions/europe-west8/subnetworks/test-nat" + } + project_ids = { + test = "foo-test-0" + } +} +project_id = "$project_ids:test" +region = "$locations:ew8" +name = "test" +vpc_config = { + network = "$networks:test" + subnetwork = "$subnets:test" +} +backends = [{ + group = "foo" + failover = false +}] +forwarding_rules_config = { + "" = { + address = "$addresses:test" + } +} +service_attachments = { + "" = { + nat_subnets = ["$subnets:test-nat"] + } +} diff --git a/tests/modules/net_lb_int/context.yaml b/tests/modules/net_lb_int/context.yaml new file mode 100644 index 000000000..ceebc8374 --- /dev/null +++ b/tests/modules/net_lb_int/context.yaml @@ -0,0 +1,126 @@ +# 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_forwarding_rule.default[""]: + all_ports: true + allow_global_access: true + allow_psc_global_access: null + description: null + ip_address: 10.0.0.10 + ip_collection: null + ip_protocol: TCP + is_mirroring_collector: null + labels: null + load_balancing_scheme: INTERNAL + name: test + network: projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0 + no_automate_dns_zone: null + ports: null + project: foo-test-0 + recreate_closed_psc: false + region: europe-west8 + service_label: null + source_ip_ranges: null + subnetwork: projects/foo-dev-net-spoke-0/regions/europe-west8/subnetworks/gce + target: null + timeouts: null + google_compute_health_check.default[0]: + check_interval_sec: 5 + description: Terraform managed. + grpc_health_check: [] + grpc_tls_health_check: [] + healthy_threshold: 2 + http2_health_check: [] + http_health_check: [] + https_health_check: [] + name: test + project: foo-test-0 + source_regions: null + ssl_health_check: [] + tcp_health_check: + - port: null + port_name: null + port_specification: USE_SERVING_PORT + proxy_header: NONE + request: null + response: null + timeout_sec: 5 + timeouts: null + unhealthy_threshold: 2 + google_compute_region_backend_service.default: + affinity_cookie_ttl_sec: null + backend: + - balancing_mode: CONNECTION + capacity_scaler: null + custom_metrics: [] + description: Terraform managed. + failover: false + group: foo + max_connections: null + max_connections_per_endpoint: null + max_connections_per_instance: null + max_rate: null + max_rate_per_endpoint: null + max_rate_per_instance: null + max_utilization: null + circuit_breakers: [] + connection_draining_timeout_sec: 300 + connection_tracking_policy: [] + consistent_hash: [] + custom_metrics: [] + description: Terraform managed. + dynamic_forwarding: [] + enable_cdn: null + failover_policy: [] + ha_policy: [] + iap: + - enabled: false + oauth2_client_id: null + oauth2_client_secret: null + ip_address_selection_policy: null + load_balancing_scheme: INTERNAL + locality_lb_policy: null + name: test + network: projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0 + outlier_detection: [] + project: foo-test-0 + protocol: UNSPECIFIED + region: europe-west8 + security_policy: null + strong_session_affinity_cookie: [] + subsetting: [] + timeouts: null + google_compute_service_attachment.default[""]: + connection_preference: ACCEPT_MANUAL + consumer_accept_lists: [] + consumer_reject_lists: null + description: Terraform managed. + domain_names: null + enable_proxy_protocol: false + name: test + nat_subnets: + - projects/foo-dev-net-spoke-0/regions/europe-west8/subnetworks/test-nat + project: foo-test-0 + region: europe-west8 + send_propagated_connection_limit_if_zero: false + timeouts: null + +counts: + google_compute_forwarding_rule: 1 + google_compute_health_check: 1 + google_compute_region_backend_service: 1 + google_compute_service_attachment: 1 + modules: 0 + resources: 4 diff --git a/tests/modules/net_lb_int/tftest.yaml b/tests/modules/net_lb_int/tftest.yaml index 5cc14ee22..0837391d7 100644 --- a/tests/modules/net_lb_int/tftest.yaml +++ b/tests/modules/net_lb_int/tftest.yaml @@ -15,5 +15,6 @@ module: modules/net-lb-int tests: + context: defaults: forwarding-rule: diff --git a/tests/modules/net_vpc/context.tfvars b/tests/modules/net_vpc/context.tfvars new file mode 100644 index 000000000..c54ff5fef --- /dev/null +++ b/tests/modules/net_vpc/context.tfvars @@ -0,0 +1,96 @@ +context = { + addresses = { + dns-external = "8.8.8.8" + dns-internal = "10.10.10.10" + test = "10.20.20.20" + } + cidr_ranges = { + rfc1918-10 = "10.0.0.0/8" + rfc1918-172 = "172.16.10.0/12" + rfc1918-192 = "192.168.0.0/16" + test = "8.8.8.8/32" + } + condition_vars = { + organization = { + id = 1234567890 + } + } + custom_roles = { + myrole = "organizations/366118655033/roles/myRoleOne" + } + iam_principals = { + test = "serviceAccount:test@test-project.iam.gserviceaccount.com" + } + locations = { + ew8 = "europe-west8" + } + networks = { + test = "projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0" + } + project_ids = { + test = "foo-test-0" + } +} +dns_policy = { + inbound = true + outbound = { + private_ns = ["$addresses:dns-internal"] + public_ns = ["$addresses:dns-external"] + } +} +internal_ranges = [ + { + name = "pods-range" + usage = "FOR_VPC" + peering = "FOR_SELF" + ip_cidr_range = "$cidr_ranges:rfc1918-172" + } +] +project_id = "$project_ids:test" +routes = { + next-hop = { + description = "Route to internal range." + dest_range = "$cidr_ranges:test" + next_hop_type = "ip" + next_hop = "$addresses:test" + } +} +subnets = [ + { + name = "production" + region = "$locations:ew8" + reserved_internal_range = "pods-range" + iam = { + "$custom_roles:myrole" = [ + "iam_principals:test" + ] + } + iam_bindings = { + myrole_two = { + role = "$custom_roles:myrole" + members = [ + "$iam_principals:test" + ] + condition = { + title = "Test" + expression = "resource.matchTag('$${organization.id}/environment', 'development')" + } + } + } + iam_bindings_additive = { + myrole_two = { + role = "$custom_roles:myrole" + member = "$iam_principals:test" + } + } + secondary_ip_ranges = { + pods = { + reserved_internal_range = "pods-range" + } + # Mixed configuration: some ranges use internal ranges, others use CIDR + traditional = { + ip_cidr_range = "$cidr_ranges:rfc1918-192" + } + } + } +] diff --git a/tests/modules/net_vpc/context.yaml b/tests/modules/net_vpc/context.yaml new file mode 100644 index 000000000..36beb5c4c --- /dev/null +++ b/tests/modules/net_vpc/context.yaml @@ -0,0 +1,175 @@ +# 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_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + enable_ula_internal_ipv6: null + name: test + network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL + network_profile: null + params: [] + project: foo-test-0 + routing_mode: GLOBAL + timeouts: null + google_compute_route.gateway["directpath-googleapis"]: + description: Terraform-managed. + dest_range: 34.126.0.0/18 + name: test-directpath-googleapis + network: test + next_hop_gateway: default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + params: [] + priority: 1000 + project: foo-test-0 + tags: null + timeouts: null + google_compute_route.gateway["private-googleapis"]: + description: Terraform-managed. + dest_range: 199.36.153.8/30 + name: test-private-googleapis + network: test + next_hop_gateway: default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + params: [] + priority: 1000 + project: foo-test-0 + tags: null + timeouts: null + google_compute_route.gateway["restricted-googleapis"]: + description: Terraform-managed. + dest_range: 199.36.153.4/30 + name: test-restricted-googleapis + network: test + next_hop_gateway: default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + params: [] + priority: 1000 + project: foo-test-0 + tags: null + timeouts: null + google_compute_route.ip["next-hop"]: + description: Route to internal range. + dest_range: 8.8.8.8/32 + name: test-next-hop + network: test + next_hop_gateway: null + next_hop_ilb: null + next_hop_instance: null + next_hop_ip: 10.20.20.20 + next_hop_vpn_tunnel: null + params: [] + priority: 1000 + project: foo-test-0 + tags: null + timeouts: null + google_compute_subnetwork.subnetwork["europe-west8/production"]: + description: Terraform-managed. + ip_collection: null + ipv6_access_type: null + log_config: [] + name: production + network: test + params: [] + private_ip_google_access: true + project: foo-test-0 + region: europe-west8 + role: null + secondary_ip_range: + - range_name: pods + - ip_cidr_range: 192.168.0.0/16 + range_name: traditional + reserved_internal_range: null + send_secondary_ip_range_if_empty: true + timeouts: null + google_compute_subnetwork_iam_binding.authoritative["europe-west8/production.organizations/366118655033/roles/myRoleOne"]: + condition: [] + members: + - iam_principals:test + project: foo-test-0 + region: europe-west8 + role: organizations/366118655033/roles/myRoleOne + subnetwork: production + google_compute_subnetwork_iam_binding.bindings["myrole_two"]: + condition: + - description: null + expression: resource.matchTag('1234567890/environment', 'development') + title: Test + members: + - serviceAccount:test@test-project.iam.gserviceaccount.com + project: foo-test-0 + region: europe-west8 + role: organizations/366118655033/roles/myRoleOne + subnetwork: production + google_compute_subnetwork_iam_member.bindings["myrole_two"]: + condition: [] + member: serviceAccount:test@test-project.iam.gserviceaccount.com + project: foo-test-0 + region: europe-west8 + role: organizations/366118655033/roles/myRoleOne + subnetwork: production + google_dns_policy.default[0]: + alternative_name_server_config: + - target_name_servers: + - forwarding_path: '' + ipv4_address: 8.8.8.8 + - forwarding_path: private + ipv4_address: 10.10.10.10 + description: Managed by Terraform + enable_inbound_forwarding: true + enable_logging: null + name: test + networks: + - {} + project: foo-test-0 + timeouts: null + google_network_connectivity_internal_range.internal_range["pods-range"]: + allocation_options: [] + description: null + effective_labels: + goog-terraform-provisioned: 'true' + exclude_cidr_ranges: null + immutable: null + ip_cidr_range: 172.16.10.0/12 + labels: null + migration: [] + name: pods-range + overlaps: null + peering: FOR_SELF + prefix_length: null + project: foo-test-0 + target_cidr_range: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + usage: FOR_VPC + +counts: + google_compute_network: 1 + google_compute_route: 4 + google_compute_subnetwork: 1 + google_compute_subnetwork_iam_binding: 2 + google_compute_subnetwork_iam_member: 1 + google_dns_policy: 1 + google_network_connectivity_internal_range: 1 + modules: 0 + resources: 11 diff --git a/tests/modules/net_vpc/tftest.yaml b/tests/modules/net_vpc/tftest.yaml index 5e9668ea4..a8cfbe17b 100644 --- a/tests/modules/net_vpc/tftest.yaml +++ b/tests/modules/net_vpc/tftest.yaml @@ -17,6 +17,7 @@ common_tfvars: - common.tfvars tests: + context: shared_vpc: psa_routes_export: psa_routes_import: diff --git a/tests/modules/net_vpc_firewall/context.tfvars b/tests/modules/net_vpc_firewall/context.tfvars new file mode 100644 index 000000000..782617fe3 --- /dev/null +++ b/tests/modules/net_vpc_firewall/context.tfvars @@ -0,0 +1,47 @@ +context = { + cidr_ranges = { + rfc1918-10 = "10.0.0.0/8" + } + iam_principals = { + test = "serviceAccount:test@test-project.iam.gserviceaccount.com" + } + networks = { + test = "projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0" + } + project_ids = { + test = "foo-test-0" + } +} +project_id = "$project_ids:test" +network = "$networks:test" +attachments = { + test = "$networks:test" +} +default_rules_config = { + admin_ranges = ["$cidr_ranges:rfc1918-10"] + http_ranges = ["$cidr_ranges:rfc1918-10"] + https_ranges = ["$cidr_ranges:rfc1918-10"] + ssh_ranges = ["$cidr_ranges:rfc1918-10"] +} +egress_rules = { + allow-egress-rfc1918 = { + deny = false + description = "Allow egress." + destination_ranges = [ + "$cidr_ranges:rfc1918-10", "172.16.0.0/12", "192.168.0.0/16" + ] + source_ranges = ["$cidr_ranges:rfc1918-10"] + targets = ["$iam_principals:test"] + use_service_accounts = true + } +} +ingress_rules = { + allow-ingress-tag = { + description = "Allow ingress." + destination_ranges = ["$cidr_ranges:rfc1918-10"] + source_ranges = ["$cidr_ranges:rfc1918-10"] + sources = ["$iam_principals:test"] + targets = ["$iam_principals:test"] + use_service_accounts = true + } +} diff --git a/tests/modules/net_vpc_firewall/context.yaml b/tests/modules/net_vpc_firewall/context.yaml new file mode 100644 index 000000000..2ad022a5a --- /dev/null +++ b/tests/modules/net_vpc_firewall/context.yaml @@ -0,0 +1,157 @@ +# 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_firewall.allow-admins[0]: + allow: + - ports: [] + protocol: all + deny: [] + description: Access from the admin subnet to all subnets. + disabled: null + log_config: [] + name: dev-spoke-0-ingress-admins + network: projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0 + params: [] + priority: 1000 + project: foo-test-0 + source_ranges: + - 10.0.0.0/8 + source_service_accounts: null + source_tags: null + target_service_accounts: null + target_tags: null + timeouts: null + google_compute_firewall.allow-tag-http[0]: + allow: + - ports: + - '80' + protocol: tcp + deny: [] + description: Allow http to machines with matching tags. + disabled: null + log_config: [] + name: dev-spoke-0-ingress-tag-http + network: projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0 + params: [] + priority: 1000 + project: foo-test-0 + source_ranges: + - 10.0.0.0/8 + source_service_accounts: null + source_tags: null + target_service_accounts: null + target_tags: + - http-server + timeouts: null + google_compute_firewall.allow-tag-https[0]: + allow: + - ports: + - '443' + protocol: tcp + deny: [] + description: Allow http to machines with matching tags. + disabled: null + log_config: [] + name: dev-spoke-0-ingress-tag-https + network: projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0 + params: [] + priority: 1000 + project: foo-test-0 + source_ranges: + - 10.0.0.0/8 + source_service_accounts: null + source_tags: null + target_service_accounts: null + target_tags: + - https-server + timeouts: null + google_compute_firewall.allow-tag-ssh[0]: + allow: + - ports: + - '22' + protocol: tcp + deny: [] + description: Allow SSH to machines with matching tags. + disabled: null + log_config: [] + name: dev-spoke-0-ingress-tag-ssh + network: projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0 + params: [] + priority: 1000 + project: foo-test-0 + source_ranges: + - 10.0.0.0/8 + source_service_accounts: null + source_tags: null + target_service_accounts: null + target_tags: + - ssh + timeouts: null + google_compute_firewall.custom-rules["allow-egress-rfc1918"]: + allow: + - ports: [] + protocol: all + deny: [] + description: Allow egress. + destination_ranges: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + direction: EGRESS + disabled: false + log_config: [] + name: allow-egress-rfc1918 + network: projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0 + params: [] + priority: 1000 + project: foo-test-0 + source_ranges: + - 10.0.0.0/8 + source_service_accounts: null + source_tags: null + target_service_accounts: + - serviceAccount:test@test-project.iam.gserviceaccount.com + target_tags: null + timeouts: null + google_compute_firewall.custom-rules["allow-ingress-tag"]: + allow: + - ports: [] + protocol: all + deny: [] + description: Allow ingress. + destination_ranges: + - 10.0.0.0/8 + direction: INGRESS + disabled: false + log_config: [] + name: allow-ingress-tag + network: projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0 + params: [] + priority: 1000 + project: foo-test-0 + source_ranges: + - 10.0.0.0/8 + source_service_accounts: + - serviceAccount:test@test-project.iam.gserviceaccount.com + source_tags: null + target_service_accounts: + - serviceAccount:test@test-project.iam.gserviceaccount.com + target_tags: null + timeouts: null + +counts: + google_compute_firewall: 6 + modules: 0 + resources: 6 diff --git a/tests/modules/net_vpc_firewall/tftest.yaml b/tests/modules/net_vpc_firewall/tftest.yaml new file mode 100644 index 000000000..5223867ff --- /dev/null +++ b/tests/modules/net_vpc_firewall/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-vpc-firewall +tests: + context: