Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

☁️ Google Cloud Cloud Router Terraform Module

Creates a single Google Cloud Router (google_compute_router) with an optional Cloud NAT gateway (google_compute_router_nat). Targets hashicorp/google ~> 7.0, Terraform >= 1.12.0.

Terraform Google Provider Module Version Module Type Resources Posture


🧩 Overview

  • 🔀 Creates one google_compute_router — the GCP resource behind the "Cloud Router" product, providing dynamic BGP routing for VPN tunnels and Interconnect/Partner attachments.
  • 🌐 Optionally creates one google_compute_router_nat — the "Cloud NAT" gateway attached to this router, giving instances without external IPs outbound internet access.
  • 🧱 Standalone, not composite: google_compute_router_nat is a single, optional 1:1 companion to the router in the common case (a router typically has zero or one Cloud NAT gateway configured against it), not a for_each-managed collection of many child records. Per this module suite's definition of standalone ("a single keystone resource with no natural child collection"), this module stays standalone — the NAT resource is rendered from a single optional object variable, never a caller-keyed map of many NAT gateways.
  • 🔒 Never defaults source_subnetwork_ip_ranges_to_nat to ALL_SUBNETWORKS_ALL_IP_RANGES — the caller must explicitly choose how much of the network NAT covers.
  • 🧭 Takes an explicit network (id/self_link from terraform-google-vpc-network) and region — both legitimate module variables here per this module suite's authentication-model exception, since they are resource-level arguments intrinsic to google_compute_router, not provider overrides.

💡 Why it matters: a Cloud Router is the control-plane resource behind both dynamic BGP routing (VPN/Interconnect) and Cloud NAT. Getting its region/network pairing right — and never silently NATing an entire network — is a foundational networking control, not an implementation detail.


❤️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!


🗺️ Where this fits

graph LR
 VPC["terraform-google-vpc-network"]:::keystoneSibling
 PS["terraform-google-project-services"]:::external
 THIS["terraform-google-cloud-router"]:::thisModule
 FUT["Future: VPN Gateway /\nInterconnect Attachment module"]:::future

 PS -. "enables compute.googleapis.com (informal prerequisite)".-> THIS
 VPC -- "network self_link / id consumed as var.network" --> THIS
 THIS -. "router self_link / name consumed by (future, not yet in catalog)".-> FUT

 classDef thisModule fill:#4285F4,color:#ffffff,stroke:#174EA6,stroke-width:1px;
 classDef keystoneSibling fill:#174EA6,color:#ffffff,stroke:#174EA6,stroke-width:1px;
 classDef external fill:#E8EAED,color:#202124,stroke:#9AA0A6,stroke-width:1px,stroke-dasharray: 3 3;
 classDef future fill:#E8EAED,color:#202124,stroke:#9AA0A6,stroke-width:1px,stroke-dasharray: 3 3;
Loading

terraform-google-project-services (external, dashed) must have already enabled compute.googleapis.com before this module applies. terraform-google-vpc-network is this module's only real cross-module input — its self_link (or id) output becomes var.network. A VPN Gateway / Interconnect Attachment module would be this router's most direct downstream consumer (of name/self_link, for BGP peer configuration), but no such module exists yet in the catalog — shown dashed as a documented future consumer, not a shipped dependency.


🧬 What this builds

graph LR
 subgraph Inputs
 A["var.router_name"]
 B["var.network"]
 C["var.region"]
 D["var.bgp (optional)"]
 E["var.nat (optional)"]
 F["var.timeouts"]
 end

 R["google_compute_router.this"]:::thisModule
 N["google_compute_router_nat.this\n(for_each 0..1, keyed on var.nat != null)"]:::optionalResource

 A --> R
 B --> R
 C --> R
 D --> R
 F --> R

 R -- "router = google_compute_router.this.name" --> N
 E -- "nat config" --> N

 R --> O1["output: id"]
 R --> O2["output: self_link"]
 R --> O3["output: name"]
 N -. "null when var.nat == null".-> O4["output: nat_name"]

 classDef thisModule fill:#4285F4,color:#ffffff,stroke:#174EA6,stroke-width:1px;
 classDef optionalResource fill:#8957E5,color:#ffffff,stroke:#174EA6,stroke-width:1px,stroke-dasharray: 3 3;
Loading

Resource inventory:

Resource Cardinality Notes
google_compute_router.this Exactly 1 Keystone; BGP config is optional (var.bgp, default null)
google_compute_router_nat.this 0 or 1 (for_each over { this = var.nat } when non-null) Never count, even for this naturally-singular resource

✅ Provider / Versions

Requirement Value
Terraform >= 1.12.0
hashicorp/google provider ~> 7.0
Provider block None — the caller configures google (project, region/zone, auth)

Schema notes that bite (verified against hashicorp/google v7.39.0 via terraform-registry, providerDocID 12683499 / 12683502):

  • A router can only BGP-peer within its own region/network pairing — a VPN tunnel or Interconnect attachment in a different region, or attached to a different network, cannot use this router.
  • router_name, network, and region are force-new — changing any of them destroys and recreates the router (and transitively the NAT gateway configured against it).
  • bgp is Optional in the live schema, not Required — a router created solely to host a Cloud NAT gateway does not need a BGP session at all. This module models that: var.bgp defaults to null.
  • The NAT subnetwork nested block's name argument is confusingly named — the provider wants the subnetwork's self-link there, not a display name. This module's field is named subnetwork_self_link to avoid that confusion.
  • nat.source_subnetwork_ip_ranges_to_nat has no default in this module — the caller must explicitly choose ALL_SUBNETWORKS_ALL_IP_RANGES, ALL_SUBNETWORKS_ALL_PRIMARY_IP_RANGES, or LIST_OF_SUBNETWORKS even though the underlying provider argument is Required regardless.
  • Neither google_compute_router nor google_compute_router_nat exposes a labels argument in the live schema — confirmed for both, symmetrically. See Architecture Notes.
  • timeouts (create/update/delete, default 20 minutes each) is supported by both resources independently — the router's own var.timeouts and the NAT's nested var.nat.timeouts.

🔑 Required IAM Roles

  • roles/compute.networkAdmin on the target project — create, update, and delete Cloud Router and Cloud NAT resources. No broader project-level role is required by this module alone.

☁️ GCP Prerequisites

  • compute.googleapis.com enabled on the target project (via terraform-google-project-services, applied before this module).
  • The target VPC network (from terraform-google-vpc-network) must already exist — this module only attaches to it via var.network, it does not create it.

📁 Module Structure

terraform-google-cloud-router/
├── providers.tf # required_providers (hashicorp/google ~> 7.0) + required_version — no provider {} block
├── variables.tf # router_name, network, region, bgp, nat, timeouts
├── main.tf # google_compute_router.this + optional google_compute_router_nat.this
├── outputs.tf # id, self_link, name, nat_name
├── README.md # this file
├── SCOPE.md # lightweight cross-module contract
└── examples/ # runnable example matching the Quick Start below

⚙️ Quick Start

module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"
}

The caller's root module configures the google provider (project, region/zone, and authentication via ADC, Workload Identity Federation, or a service account key supplied out-of-band) — this module accepts none of those as variables.


🔌 Cross-Module Contract

Consumes

Input Type Source module
network string (self_link or id) terraform-google-vpc-network

Emits

Output Description Consumed by
id Router resource id, projects/{{project}}/regions/{{region}}/routers/{{name}} Any module needing the Terraform-internal reference
self_link Router self-link (URL form) Future VPN/Interconnect modules' router field
name Router name Future VPN/Interconnect modules' router field (name form)
nat_name Name of the NAT gateway, or null if var.nat was not supplied Diagnostic/reference use; conditional

📚 Example Library

1 · Minimal router — no BGP, no NAT
module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"
}

💡 The empty-beyond-required call is the safe, complete path: no BGP session and no NAT gateway are created. This is a valid, working Cloud Router — useful as a placeholder that a later apply can extend with bgp/nat without recreating the resource.

2 · Router with a default-mode BGP session
module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"

  bgp = {
    asn = 64514
  }
}

ℹ️ advertise_mode defaults to DEFAULT — GCP automatically advertises the router's own subnet ranges. Use CUSTOM mode (example 8) to advertise specific groups/ranges instead.

3 · Router + NAT — ALL_SUBNETWORKS_ALL_IP_RANGES, explicit choice
module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"

  nat = {
    name                               = "us-east1-nat"
    source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
  }
}

⚠️ ALL_SUBNETWORKS_ALL_IP_RANGES NATs every IP range in every subnetwork in this router's region — this module requires the caller to type this value explicitly (there is no default), so this broad a scope is always a conscious choice, never an accident.

4 · Router + NAT — ALL_SUBNETWORKS_ALL_PRIMARY_IP_RANGES, explicit choice
module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"

  nat = {
    name                               = "us-east1-nat"
    source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_PRIMARY_IP_RANGES"
  }
}

ℹ️ Narrower than example 3 — only each subnetwork's primary IP range is NATed, excluding secondary (alias) ranges such as GKE pod/service ranges.

5 · Router + NAT — LIST_OF_SUBNETWORKS, single subnetwork
module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"

  nat = {
    name                               = "us-east1-nat"
    source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS"

    subnetworks = {
      "app-subnet" = {
        subnetwork_self_link    = module.vpc_network.subnetwork_self_links["app-subnet-use1"]
        source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
      }
    }
  }
}

🔒 The narrowest, most auditable NAT scope — only the named subnetwork(s) get outbound NAT. This module's validation {} block rejects an empty subnetworks map whenever LIST_OF_SUBNETWORKS is chosen.

6 · Router + NAT — LIST_OF_SUBNETWORKS, multiple subnetworks with secondary ranges
module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"

  nat = {
    name                               = "us-east1-nat"
    source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS"

    subnetworks = {
      "gke-nodes" = {
        subnetwork_self_link    = module.vpc_network.subnetwork_self_links["gke-nodes-use1"]
        source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
      }
      "app-subnet" = {
        subnetwork_self_link     = module.vpc_network.subnetwork_self_links["app-subnet-use1"]
        source_ip_ranges_to_nat  = ["LIST_OF_SECONDARY_IP_RANGES"]
        secondary_ip_range_names = ["pods", "services"]
      }
    }
  }
}

ℹ️ secondary_ip_range_names is only meaningful when the corresponding entry's source_ip_ranges_to_nat includes LIST_OF_SECONDARY_IP_RANGES — otherwise it is ignored by the provider.

7 · Router + NAT — MANUAL_ONLY with static NAT IPs
resource "google_compute_address" "nat_ip" {
  name   = "us-east1-nat-ip"
  region = "us-east1"

  lifecycle {
    create_before_destroy = true
  }
}

module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"

  nat = {
    name                               = "us-east1-nat"
    source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
    nat_ip_allocate_option             = "MANUAL_ONLY"
    nat_ips                            = [google_compute_address.nat_ip.self_link]
  }
}

⚠️ Per the live provider docs, recreating a google_compute_address used by google_compute_router_nat raises a resourceInUseByAnotherResource error — the lifecycle.create_before_destroy = true block shown above is required on the address resource to avoid it.

8 · Custom BGP advertisement mode
module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"

  bgp = {
    asn               = 64514
    advertise_mode    = "CUSTOM"
    advertised_groups = ["ALL_SUBNETS"]
    advertised_ip_ranges = [
      { range = "192.168.100.0/24", description = "on-prem DR site" },
      { range = "10.50.0.0/16" },
    ]
  }
}

ℹ️ advertised_groups and advertised_ip_ranges are only applied when advertise_mode = "CUSTOM"; this module's validation {} block rejects any advertised_groups value other than "ALL_SUBNETS" (the only value the live schema accepts).

9 · BGP keepalive interval and explicit identifier range
module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"

  bgp = {
    asn                = 64514
    keepalive_interval = 60
    identifier_range   = "169.254.0.0/29"
  }
}

⚠️ keepalive_interval must be between 20 and 60 seconds (enforced via validation {}); identifier_range must not overlap any IPv4 BGP session range already in use on this router.

10 · Router at the 32-bit private ASN boundary
module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router-32bit-asn"
  network     = module.vpc_network.self_link
  region      = "us-east1"

  bgp = {
    asn = 4200000000 # minimum of the 32-bit private ASN range
  }
}

⚠️ asn must fall within either the 16-bit private range (64512-65534) or the 32-bit private range (4200000000-4294967294) per RFC6996 — a value outside both ranges fails this module's validation {} block at plan time, before any API call.

11 · NAT logging widened to ALL
module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"

  nat = {
    name                               = "us-east1-nat"
    source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"

    log_config = {
      enable = true
      filter = "ALL"
    }
  }
}

🔒 The default is { enable = true, filter = "ERRORS_ONLY" } — logging is on by default per this module suite's audit-logging secure-default rule, at the least noisy level. Widen to TRANSLATIONS_ONLY or ALL explicitly if full connection visibility is required (higher log volume and cost).

12 · Higher port allocation per VM
module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"

  nat = {
    name                               = "us-east1-nat"
    source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
    min_ports_per_vm                   = 256
  }
}

ℹ️ Raise min_ports_per_vm above the provider's default of 64 for workloads that open many concurrent outbound connections per instance (e.g. high-fanout microservices); this reduces the number of instances a single NAT IP can support.

13 · Custom router and NAT timeouts
module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "us-east1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"

  timeouts = {
    create = "30m"
    update = "30m"
    delete = "30m"
  }

  nat = {
    name                               = "us-east1-nat"
    source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"

    timeouts = {
      create = "30m"
      delete = "30m"
    }
  }
}

ℹ️ The router's var.timeouts and the NAT's nested var.nat.timeouts are entirely independent — each resource has its own schema-supported create/update/delete timeout, defaulting to 20 minutes each if left unset.

14 · 🏗️ End-to-end composition
module "project_services" {
  source = "git::https://github.com/microsoftexpert/terraform-google-project-services.git?ref=v1.0.0"

  # Enables compute.googleapis.com (and any other APIs the composition needs)
  # — applied before every other module in this composition.
}

module "vpc_network" {
  source = "git::https://github.com/microsoftexpert/terraform-google-vpc-network.git?ref=v1.0.0"

  network_name = "prod-use1-network"

  subnetworks = {
    "app-subnet-use1" = {
      ip_cidr_range = "10.0.1.0/24"
      region        = "us-east1"
    }
  }

  depends_on = [module.project_services]
}

module "cloud_router" {
  source = "git::https://github.com/microsoftexpert/terraform-google-cloud-router.git?ref=v1.0.0"

  router_name = "prod-use1-router"
  network     = module.vpc_network.self_link
  region      = "us-east1"

  bgp = {
    asn = 64514
  }

  nat = {
    name                               = "prod-use1-nat"
    source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS"

    subnetworks = {
      "app-subnet" = {
        subnetwork_self_link    = module.vpc_network.subnetwork_self_links["app-subnet-use1"]
        source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
      }
    }
  }
}

💡 This wires terraform-google-project-servicesterraform-google-vpc-networkterraform-google-cloud-router in dependency order: APIs enabled first, then the network and its subnetwork, then a router in the same region attached to that network via module.vpc_network.self_link, with a Cloud NAT scoped narrowly (LIST_OF_SUBNETWORKS) to only the one subnetwork that needs outbound internet access.

⚠️ region = "us-east1" on the router must match the region of every subnetwork referenced in nat.subnetworks — a router cannot NAT a subnetwork in a different region. See Troubleshooting.


📥 Inputs

Variable Type Required Default Notes
router_name string Yes Force-new; RFC1035, 1-63 chars
network string Yes self_link or id from terraform-google-vpc-network; force-new
region string Yes Force-new; router/NAT can only peer within this region
bgp object({...}) No null Omit for a NAT-only router (Cloud NAT does not require BGP)
nat object({...}) No null source_subnetwork_ip_ranges_to_nat has no default — explicit choice required
timeouts object({ create, update, delete = optional(string) }) No null Router's own timeouts; NAT has an independent nat.timeouts
Full variable schemas
variable "router_name" {
  type = string
  # RFC1035, 1-63 chars, enforced via validation {}
}

variable "network" {
  type = string
}

variable "region" {
  type = string
}

variable "bgp" {
  type = object({
    asn               = number
    advertise_mode    = optional(string, "DEFAULT")
    advertised_groups = optional(list(string), [])
    advertised_ip_ranges = optional(list(object({
      range       = string
      description = optional(string)
    })), [])
    keepalive_interval = optional(number, 20)
    identifier_range   = optional(string)
  })
  default = null
}

variable "nat" {
  type = object({
    name                               = string
    source_subnetwork_ip_ranges_to_nat = string
    nat_ip_allocate_option             = optional(string, "AUTO_ONLY")
    nat_ips                            = optional(list(string), [])
    min_ports_per_vm                   = optional(number, 64)
    subnetworks = optional(map(object({
      subnetwork_self_link     = string
      source_ip_ranges_to_nat  = list(string)
      secondary_ip_range_names = optional(list(string), [])
    })), {})
    log_config = optional(object({
      enable = optional(bool, true)
      filter = optional(string, "ERRORS_ONLY")
    }), { enable = true, filter = "ERRORS_ONLY" })
    timeouts = optional(object({
      create = optional(string)
      update = optional(string)
      delete = optional(string)
    }))
  })
  default = null
}

variable "timeouts" {
  type = object({
    create = optional(string)
    update = optional(string)
    delete = optional(string)
  })
  default = null
}

No labels variable exists on this module — see Architecture Notes for why.


🧾 Outputs

Output Description
id projects/{{project}}/regions/{{region}}/routers/{{name}}
self_link Router self-link (URL form)
name Router name
nat_name NAT gateway name, or null when var.nat was not supplied (conditional)

None of these outputs are secret-bearing; no sensitive = true is applied to any of them.


🧠 Architecture Notes

  • Region/network BGP-peering constraint. A Cloud Router can only BGP-peer with VPN tunnels or Interconnect attachments in its own region and attached network. This is not enforced by terraform validate — it fails only at apply (or at BGP session establishment time), per the house "plan-only posture" limitation.
  • bgp is optional by design. The live schema marks bgp Optional, and this module preserves that: a router created solely to anchor a Cloud NAT gateway does not need a BGP session. Do not assume every router in this library carries BGP configuration.
  • NAT subnetwork.name is actually a self-link. The provider's nested subnetwork block uses the argument name name, but the value it expects is the subnetwork's self-link, not a display name. This module's own field is named subnetwork_self_link specifically to avoid propagating that confusing name into caller-facing configuration.
  • No silent "NAT everything." nat.source_subnetwork_ip_ranges_to_nat has no default in this module — every caller must type one of the three valid values explicitly. This is a deliberate deviation from simply passing through the provider's own (Required-but-otherwise-unguided) argument.
  • No labels variable — confirmed schema-driven omission, symmetric across both resources. Verified against the live schema (hashicorp/google v7.39.0) that neither google_compute_router nor google_compute_router_nat exposes a labels argument. Per this module suite's own post-module checklist, this module documents the omission rather than fabricating a decorative variable with no effect on either resource.
  • timeouts is modeled independently per resource. The router's own var.timeouts and the NAT's nested var.nat.timeouts both support create/update/delete (20-minute provider default each) — they are not shared or coupled.
  • nat_name is conditional. It resolves to null whenever var.nat is not supplied; any consumer of this output must check for null before using it.

🧱 Design Principles

Concern Secure default Opt-out (explicit)
NAT subnetwork scope (source_subnetwork_ip_ranges_to_nat) No default — caller must explicitly choose ALL_SUBNETWORKS_ALL_IP_RANGES, ALL_SUBNETWORKS_ALL_PRIMARY_IP_RANGES, or LIST_OF_SUBNETWORKS N/A — this is itself the opt-out mechanism; there is no narrower "safe" value to fall back to silently
NAT logging (nat.log_config) { enable = true, filter = "ERRORS_ONLY" } — enabled by default at the least noisy level Caller sets filter = "TRANSLATIONS_ONLY" or "ALL" for full visibility, or enable = false to disable
NAT IP allocation (nat.nat_ip_allocate_option) AUTO_ONLY — GCP-managed IP allocation, no static IP resource to manage Caller sets MANUAL_ONLY and supplies nat_ips (self-links of pre-created google_compute_address resources)
BGP session (bgp) null — no BGP session unless explicitly configured Caller supplies bgp = { asn =... }
Deletion guard Not applicable — google_compute_router/google_compute_router_nat expose no deletion_protection argument in the live schema N/A

🚀 Runbook

cd terraform-google-cloud-router
terraform init -backend=false
terraform validate
terraform fmt -check

Pin the module source to ?ref=v1.0.0 — never a branch. This library is plan-only from an authoring session; a human applies from CI with valid Workload Identity Federation or ADC credentials.


🧪 Testing

terraform validate confirms internal type and reference consistency (e.g. router_name's and nat.name's validation {} blocks, the bgp/nat object schemas, correct resource/output wiring). terraform fmt -check confirms canonical formatting. Neither can catch GCP API-level rejections — most importantly, the region/network BGP-peering constraint, quota limits, or org-policy constraints. Only a real terraform plan/apply against a live project, with valid credentials, exercises those paths — that step belongs to the consuming CI pipeline, not this authoring session.


💬 Example Output

$ terraform output

id = "projects/casey-prod-networking/regions/us-east1/routers/prod-use1-router"
name = "prod-use1-router"
nat_name = "prod-use1-nat"
self_link = "https://www.googleapis.com/compute/v1/projects/casey-prod-networking/regions/us-east1/routers/prod-use1-router"

🔍 Troubleshooting

Symptom Cause Fix
apply fails with a region/network mismatch error on a BGP peer or NAT subnetwork The router can only BGP-peer or NAT within its own region/network pairing Ensure var.region matches the region of every subnetwork referenced in nat.subnetworks, and that var.network is the same network those subnetworks belong to
plan fails with a nat.source_subnetwork_ip_ranges_to_nat validation error Value omitted or not one of the three valid enums Set it explicitly to ALL_SUBNETWORKS_ALL_IP_RANGES, ALL_SUBNETWORKS_ALL_PRIMARY_IP_RANGES, or LIST_OF_SUBNETWORKS
plan fails with "nat.subnetworks must contain at least one entry" source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" but subnetworks was left empty Supply at least one entry in nat.subnetworks
plan fails with a bgp.asn validation error ASN outside the RFC6996 private ranges (64512-65534 or 4200000000-4294967294) Choose an ASN within one of the two private ranges
apply fails with resourceInUseByAnotherResource on a google_compute_address used by NAT Recreating a static NAT IP address that Cloud NAT is actively using Add lifecycle { create_before_destroy = true } to the google_compute_address resource (see example 7)
nat_name output is null when a NAT gateway was expected var.nat was not supplied on this apply Confirm nat = {... } is set; nat_name is null by design whenever var.nat == null
No labels variable in variables.tf — looks incomplete compared to other modules Deliberate: neither google_compute_router nor google_compute_router_nat exposes a labels argument in the live schema Confirmed against the live provider schema; see Architecture Notes

🔗 Related Docs

About

Terraform module: terraform-google-cloud-router

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages