diff --git a/examples/cloud-operations/packer-image-builder/packer/README.md b/examples/cloud-operations/packer-image-builder/packer/README.md
index 68649734b..18bcaece0 100644
--- a/examples/cloud-operations/packer-image-builder/packer/README.md
+++ b/examples/cloud-operations/packer-image-builder/packer/README.md
@@ -7,17 +7,6 @@ The example uses following GCP features:
* [service account impersonation](https://cloud.google.com/iam/docs/impersonating-service-accounts)
* [Identity-Aware Proxy](https://cloud.google.com/iap/docs/using-tcp-forwarding) tunnel
-
-## Variables
-| name | description | type | required | default |
-|---|---|:---: |:---:|:---:|
-| builder_sa | Image builder's service account email. | string | ✓ | |
-| compute_sa | Temporary's VM service account email. | string | ✓ | |
-| compute_subnetwork | Name of a VPC subnetwork for temporary VM instance. | string | ✓ | |
-| compute_zone | Compute Engine zone to run temporary VM instance. | string | ✓ | |
-| project_id | Project id that references existing GCP project. | string | ✓ | |
-| *use_iap* | Indicates to use IAP tunnel for communication with temporary VM instance. | bool | | true |
-
-
\ No newline at end of file
+
diff --git a/examples/factories/project-factory/main.tf b/examples/factories/project-factory/main.tf
index 5f8356fc6..4efdaeac0 100644
--- a/examples/factories/project-factory/main.tf
+++ b/examples/factories/project-factory/main.tf
@@ -139,7 +139,7 @@ module "billing-alert" {
module "dns" {
source = "../../../modules/dns"
for_each = toset(var.dns_zones)
- project_id = module.project.project_id
+ project_id = coalesce(local.vpc.host_project, module.project.project_id)
type = "private"
name = each.value
domain = "${each.value}.${var.defaults.environment_dns_zone}"
diff --git a/fast/stages/02-networking-nva/data/firewall-rules/dev/rules.yaml b/fast/stages/02-networking-nva/data/firewall-rules/dev/rules.yaml
index f2d614a59..d4df8cdc3 100644
--- a/fast/stages/02-networking-nva/data/firewall-rules/dev/rules.yaml
+++ b/fast/stages/02-networking-nva/data/firewall-rules/dev/rules.yaml
@@ -5,7 +5,7 @@ ingress-allow-composer-nodes:
direction: INGRESS
action: allow
sources: []
- ranges: []
+ ranges: ["0.0.0.0/0"]
targets:
- composer-worker
use_service_accounts: false
@@ -18,7 +18,7 @@ ingress-allow-dataflow-load:
direction: INGRESS
action: allow
sources: []
- ranges: []
+ ranges: ["0.0.0.0/0"]
targets:
- dataflow
use_service_accounts: false
diff --git a/fast/stages/02-networking-nva/dns-dev.tf b/fast/stages/02-networking-nva/dns-dev.tf
index 08de34cce..08c99486f 100644
--- a/fast/stages/02-networking-nva/dns-dev.tf
+++ b/fast/stages/02-networking-nva/dns-dev.tf
@@ -20,11 +20,11 @@
module "dev-dns-private-zone" {
source = "../../../modules/dns"
- project_id = module.landing-project.project_id
+ project_id = module.dev-spoke-project.project_id
type = "private"
name = "dev-gcp-example-com"
domain = "dev.gcp.example.com."
- client_networks = [module.dev-spoke-vpc.self_link]
+ client_networks = [module.landing-trusted-vpc.self_link, module.landing-untrusted-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
}
diff --git a/fast/stages/02-networking-nva/dns-landing.tf b/fast/stages/02-networking-nva/dns-landing.tf
index f13ef9960..f177dcda2 100644
--- a/fast/stages/02-networking-nva/dns-landing.tf
+++ b/fast/stages/02-networking-nva/dns-landing.tf
@@ -59,34 +59,6 @@ module "gcp-example-dns-private-zone" {
}
}
-# GCP-specific DNS zones peered to the environment spoke that holds the config
-
-module "prod-gcp-example-dns-peering" {
- source = "../../../modules/dns"
- project_id = module.landing-project.project_id
- type = "peering"
- name = "prod-root-dns-peering"
- domain = "prod.gcp.example.com."
- client_networks = [
- module.landing-untrusted-vpc.self_link,
- module.landing-trusted-vpc.self_link
- ]
- peer_network = module.prod-spoke-vpc.self_link
-}
-
-module "dev-gcp-example-dns-peering" {
- source = "../../../modules/dns"
- project_id = module.landing-project.project_id
- type = "peering"
- name = "dev-root-dns-peering"
- domain = "dev.gcp.example.com."
- client_networks = [
- module.landing-untrusted-vpc.self_link,
- module.landing-trusted-vpc.self_link
- ]
- peer_network = module.dev-spoke-vpc.self_link
-}
-
# Google API zone to trigger Private Access
module "googleapis-private-zone" {
diff --git a/fast/stages/02-networking-nva/dns-prod.tf b/fast/stages/02-networking-nva/dns-prod.tf
index d92157e82..335f1508e 100644
--- a/fast/stages/02-networking-nva/dns-prod.tf
+++ b/fast/stages/02-networking-nva/dns-prod.tf
@@ -20,11 +20,11 @@
module "prod-dns-private-zone" {
source = "../../../modules/dns"
- project_id = module.landing-project.project_id
+ project_id = module.prod-spoke-project.project_id
type = "private"
name = "prod-gcp-example-com"
domain = "prod.gcp.example.com."
- client_networks = [module.prod-spoke-vpc.self_link]
+ client_networks = [module.landing-trusted-vpc.self_link, module.landing-untrusted-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
}
diff --git a/fast/stages/02-networking-nva/outputs.tf b/fast/stages/02-networking-nva/outputs.tf
index 0b93bf4b2..3cd187b0f 100644
--- a/fast/stages/02-networking-nva/outputs.tf
+++ b/fast/stages/02-networking-nva/outputs.tf
@@ -68,11 +68,11 @@ output "vpn_gateway_endpoints" {
description = "External IP Addresses for the GCP VPN gateways."
value = {
onprem-ew1 = {
- for v in module.landing-to-onprem-ew1-vpn.gateway.vpn_interfaces :
+ for v in module.landing-to-onprem-ew1-vpn[0].gateway.vpn_interfaces :
v.id => v.ip_address
}
onprem-ew4 = {
- for v in module.landing-to-onprem-ew4-vpn.gateway.vpn_interfaces :
+ for v in module.landing-to-onprem-ew4-vpn[0].gateway.vpn_interfaces :
v.id => v.ip_address
}
}
diff --git a/fast/stages/02-networking-nva/vpn-onprem.tf b/fast/stages/02-networking-nva/vpn-onprem.tf
index d28a66ee4..c860c0998 100644
--- a/fast/stages/02-networking-nva/vpn-onprem.tf
+++ b/fast/stages/02-networking-nva/vpn-onprem.tf
@@ -17,7 +17,8 @@
# tfdoc:file:description VPN between landing and onprem.
locals {
- bgp_peer_options_onprem = {
+ enable_onprem_vpn = var.vpn_onprem_configs != null
+ bgp_peer_options_onprem = local.enable_onprem_vpn == false ? null : {
for k, v in var.vpn_onprem_configs :
k => v.adv == null ? null : {
advertise_groups = []
@@ -32,6 +33,7 @@ locals {
}
module "landing-to-onprem-ew1-vpn" {
+ count = local.enable_onprem_vpn ? 1 : 0
source = "../../../modules/net-vpn-ha"
project_id = module.landing-project.project_id
network = module.landing-trusted-vpc.self_link
@@ -60,6 +62,7 @@ module "landing-to-onprem-ew1-vpn" {
}
module "landing-to-onprem-ew4-vpn" {
+ count = local.enable_onprem_vpn ? 1 : 0
source = "../../../modules/net-vpn-ha"
project_id = module.landing-project.project_id
network = module.landing-trusted-vpc.self_link
diff --git a/fast/stages/02-networking-peering/.gitignore b/fast/stages/02-networking-peering/.gitignore
new file mode 100644
index 000000000..19af5b744
--- /dev/null
+++ b/fast/stages/02-networking-peering/.gitignore
@@ -0,0 +1 @@
+ludo-*
diff --git a/fast/stages/02-networking-peering/IAM.md b/fast/stages/02-networking-peering/IAM.md
new file mode 100644
index 000000000..f5c690672
--- /dev/null
+++ b/fast/stages/02-networking-peering/IAM.md
@@ -0,0 +1,16 @@
+# IAM bindings reference
+
+Legend: + additive, • conditional.
+
+## Project dev-net-spoke-0
+
+| members | roles |
+|---|---|
+|dev-resman-pf-0
serviceAccount|[roles/resourcemanager.projectIamAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectIamAdmin) •
[roles/dns.admin](https://cloud.google.com/iam/docs/understanding-roles#dns.admin) |
+|prod-resman-pf-0
serviceAccount|organizations/[org_id #0]/roles/serviceProjectNetworkAdmin |
+
+## Project prod-net-spoke-0
+
+| members | roles |
+|---|---|
+|prod-resman-pf-0
serviceAccount|[roles/resourcemanager.projectIamAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectIamAdmin) •
organizations/[org_id #0]/roles/serviceProjectNetworkAdmin
[roles/dns.admin](https://cloud.google.com/iam/docs/understanding-roles#dns.admin) |
diff --git a/fast/stages/02-networking-peering/README.md b/fast/stages/02-networking-peering/README.md
new file mode 100644
index 000000000..2b3e98800
--- /dev/null
+++ b/fast/stages/02-networking-peering/README.md
@@ -0,0 +1,317 @@
+# Networking
+
+This stage sets up the shared network infrastructure for the whole organization. It adopts the common “hub and spoke” reference design, which is well suited to multiple scenarios, and offers several advantages versus other designs:
+
+- the “hub” VPC centralizes external connectivity to on-prem or other cloud environments, and is ready to host cross-environment services like CI/CD, code repositories, and monitoring probes
+- the “spoke” VPCs allow partitioning workloads (e.g. by environment like in this setup), while still retaining controlled access to central connectivity and services
+- Shared VPC in both hub and spokes splits management of network resources in specific (host) projects, while still allowing them to be consumed from workload (service) projects
+- the design also lends itself to easy DNS centralization, both from on-prem to cloud and from cloud to on-prem
+
+Connectivity between hub and spokes is established here via [VPC Peering](https://cloud.google.com/vpc/docs/vpc-peering), which offers a complete isolation between environments, and no choke-points in the data plane. Different ways of implementing connectivity, and their respective pros and cons, are discussed below.
+
+The following diagram illustrates the high-level design, and should be used as a reference for the following sections. The final number of subnets, and their IP addressing design will of course depend on customer-specific requirements, and can be easily changed via variables or external data files without having to edit the actual code.
+
+
+
+
dns | |
+| [dns-landing.tf](./dns-landing.tf) | Landing DNS zones and peerings setup. | dns | |
+| [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | dns | |
+| [landing.tf](./landing.tf) | Landing VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | |
+| [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder | |
+| [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard |
+| [outputs.tf](./outputs.tf) | Module outputs. | | local_file |
+| [peerings.tf](./peerings.tf) | None | net-vpc-peering | |
+| [spoke-dev.tf](./spoke-dev.tf) | Dev spoke VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | google_project_iam_binding |
+| [spoke-prod.tf](./spoke-prod.tf) | Production spoke VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | google_project_iam_binding |
+| [test-resources.tf](./test-resources.tf) | temporary instances for testing | compute-vm | |
+| [variables-peerings.tf](./variables-peerings.tf) | Peering related variables. | | |
+| [variables.tf](./variables.tf) | Module variables. | | |
+| [vpn-onprem.tf](./vpn-onprem.tf) | VPN between landing and onprem. | net-vpn-ha | |
+
+## Variables
+
+| name | description | type | required | default | producer |
+|---|---|:---:|:---:|:---:|:---:|
+| [billing_account](variables.tf#L17) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap |
+| [folder_ids](variables.tf#L66) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 01-resman |
+| [organization](variables.tf#L94) | Organization details. | object({…}) | ✓ | | 00-bootstrap |
+| [prefix](variables.tf#L110) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap |
+| [custom_adv](variables.tf#L26) | Custom advertisement definitions in name => range format. | map(string) | | {…} | |
+| [custom_roles](variables.tf#L43) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 00-bootstrap |
+| [data_dir](variables.tf#L52) | Relative path for the folder storing configuration data for network resources. | string | | "data" | |
+| [dns](variables.tf#L58) | Onprem DNS resolvers. | map(list(string)) | | {…} | |
+| [l7ilb_subnets](variables.tf#L76) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | |
+| [outputs_location](variables.tf#L104) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | |
+| [peering_configs](variables-peerings.tf#L19) | Peering configurations. | map(object({…})) | | {…} | |
+| [psa_ranges](variables.tf#L121) | IP ranges used for Private Service Access (e.g. CloudSQL). | map(map(string)) | | {…} | |
+| [router_onprem_configs](variables.tf#L136) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | |
+| [service_accounts](variables.tf#L154) | Automation service accounts in name => email format. | object({…}) | | null | 01-resman |
+| [vpn_onprem_configs](variables.tf#L166) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | |
+
+## Outputs
+
+| name | description | sensitive | consumers |
+|---|---|:---:|---|
+| [cloud_dns_inbound_policy](outputs.tf#L57) | IP Addresses for Cloud DNS inbound policy. | | |
+| [host_project_ids](outputs.tf#L62) | Network project ids. | | |
+| [host_project_numbers](outputs.tf#L67) | Network project numbers. | | |
+| [shared_vpc_self_links](outputs.tf#L72) | Shared VPC host projects. | | |
+| [tfvars](outputs.tf#L87) | Terraform variables file for the following stages. | ✓ | |
+| [vpn_gateway_endpoints](outputs.tf#L77) | External IP Addresses for the GCP VPN gateways. | | |
+
+
diff --git a/fast/stages/02-networking-peering/data/cidrs.yaml b/fast/stages/02-networking-peering/data/cidrs.yaml
new file mode 100644
index 000000000..b6c25e21a
--- /dev/null
+++ b/fast/stages/02-networking-peering/data/cidrs.yaml
@@ -0,0 +1,15 @@
+# skip boilerplate check
+
+healthchecks:
+ - 35.191.0.0/16
+ - 130.211.0.0/22
+ - 209.85.152.0/22
+ - 209.85.204.0/22
+
+rfc1918:
+ - 10.0.0.0/8
+ - 172.16.0.0/12
+ - 192.168.0.0/16
+
+onprem_probes:
+ - 10.255.255.254/32
diff --git a/fast/stages/02-networking-peering/data/dashboards/firewall_insights.json b/fast/stages/02-networking-peering/data/dashboards/firewall_insights.json
new file mode 100644
index 000000000..e829091cf
--- /dev/null
+++ b/fast/stages/02-networking-peering/data/dashboards/firewall_insights.json
@@ -0,0 +1,68 @@
+{
+ "displayName": "Firewall Insights Monitoring",
+ "gridLayout": {
+ "columns": "2",
+ "widgets": [
+ {
+ "title": "Subnet Firewall Hit Counts",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"firewallinsights.googleapis.com/subnet/firewall_hit_count\" resource.type=\"gce_subnetwork\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "1"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "VM Firewall Hit Counts",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"firewallinsights.googleapis.com/vm/firewall_hit_count\" resource.type=\"gce_instance\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "1"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/fast/stages/02-networking-peering/data/dashboards/vpn.json b/fast/stages/02-networking-peering/data/dashboards/vpn.json
new file mode 100644
index 000000000..4396cc00b
--- /dev/null
+++ b/fast/stages/02-networking-peering/data/dashboards/vpn.json
@@ -0,0 +1,248 @@
+{
+ "displayName": "VPN Monitoring",
+ "gridLayout": {
+ "columns": "2",
+ "widgets": [
+ {
+ "title": "Number of connections",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_MEAN"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/gateway/connections\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "1"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Tunnel established",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_MEAN"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/tunnel_established\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "1"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Cloud VPN Gateway - Received bytes",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/network/received_bytes_count\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "By"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Cloud VPN Gateway - Sent bytes",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/network/sent_bytes_count\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "By"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Cloud VPN Gateway - Received packets",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/network/received_packets_count\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "{packets}"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Cloud VPN Gateway - Sent packets",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/network/sent_packets_count\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "{packets}"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Incoming packets dropped",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/network/dropped_received_packets_count\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "1"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Outgoing packets dropped",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/network/dropped_sent_packets_count\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "1"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/fast/stages/02-networking-peering/data/firewall-rules/dev/rules.yaml b/fast/stages/02-networking-peering/data/firewall-rules/dev/rules.yaml
new file mode 100644
index 000000000..d4df8cdc3
--- /dev/null
+++ b/fast/stages/02-networking-peering/data/firewall-rules/dev/rules.yaml
@@ -0,0 +1,27 @@
+# skip boilerplate check
+
+ingress-allow-composer-nodes:
+ description: "Allow traffic to Composer nodes."
+ direction: INGRESS
+ action: allow
+ sources: []
+ ranges: ["0.0.0.0/0"]
+ targets:
+ - composer-worker
+ use_service_accounts: false
+ rules:
+ - protocol: tcp
+ ports: [80, 443, 3306, 3307]
+
+ingress-allow-dataflow-load:
+ description: "Allow traffic to Dataflow nodes."
+ direction: INGRESS
+ action: allow
+ sources: []
+ ranges: ["0.0.0.0/0"]
+ targets:
+ - dataflow
+ use_service_accounts: false
+ rules:
+ - protocol: tcp
+ ports: [12345, 12346]
diff --git a/fast/stages/02-networking-peering/data/firewall-rules/landing/rules.yaml b/fast/stages/02-networking-peering/data/firewall-rules/landing/rules.yaml
new file mode 100644
index 000000000..e72b7c9c7
--- /dev/null
+++ b/fast/stages/02-networking-peering/data/firewall-rules/landing/rules.yaml
@@ -0,0 +1,15 @@
+# skip boilerplate check
+
+allow-onprem-probes-example:
+ description: "Allow traffic from onprem probes"
+ direction: INGRESS
+ action: allow
+ sources: []
+ ranges:
+ - $onprem_probes
+ targets: []
+ use_service_accounts: false
+ rules:
+ - protocol: tcp
+ ports:
+ - 12345
diff --git a/fast/stages/02-networking-peering/data/hierarchical-policy-rules.yaml b/fast/stages/02-networking-peering/data/hierarchical-policy-rules.yaml
new file mode 100644
index 000000000..0172a3091
--- /dev/null
+++ b/fast/stages/02-networking-peering/data/hierarchical-policy-rules.yaml
@@ -0,0 +1,49 @@
+# skip boilerplate check
+
+allow-admins:
+ description: Access from the admin subnet to all subnets
+ direction: INGRESS
+ action: allow
+ priority: 1000
+ ranges:
+ - $rfc1918
+ ports:
+ all: []
+ target_resources: null
+ enable_logging: false
+
+allow-healthchecks:
+ description: Enable HTTP and HTTPS healthchecks
+ direction: INGRESS
+ action: allow
+ priority: 1001
+ ranges:
+ - $healthchecks
+ ports:
+ tcp: ["80", "443"]
+ target_resources: null
+ enable_logging: false
+
+allow-ssh-from-iap:
+ description: Enable SSH from IAP
+ direction: INGRESS
+ action: allow
+ priority: 1002
+ ranges:
+ - 35.235.240.0/20
+ ports:
+ tcp: ["22"]
+ target_resources: null
+ enable_logging: false
+
+allow-icmp:
+ description: Enable ICMP
+ direction: INGRESS
+ action: allow
+ priority: 1003
+ ranges:
+ - 0.0.0.0/0
+ ports:
+ icmp: []
+ target_resources: null
+ enable_logging: false
diff --git a/fast/stages/02-networking-peering/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/02-networking-peering/data/subnets/dev/dev-dataplatform-ew1.yaml
new file mode 100644
index 000000000..92994826d
--- /dev/null
+++ b/fast/stages/02-networking-peering/data/subnets/dev/dev-dataplatform-ew1.yaml
@@ -0,0 +1,8 @@
+# skip boilerplate check
+
+region: europe-west1
+description: Default subnet for dev Data Platform
+ip_cidr_range: 10.128.48.0/24
+secondary_ip_range:
+ pods: 100.128.48.0/20
+ services: 100.255.48.0/24
diff --git a/fast/stages/02-networking-peering/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/02-networking-peering/data/subnets/dev/dev-default-ew1.yaml
new file mode 100644
index 000000000..8b066ba70
--- /dev/null
+++ b/fast/stages/02-networking-peering/data/subnets/dev/dev-default-ew1.yaml
@@ -0,0 +1,5 @@
+# skip boilerplate check
+
+region: europe-west1
+ip_cidr_range: 10.128.32.0/24
+description: Default subnet for dev
diff --git a/fast/stages/02-networking-peering/data/subnets/landing/landing-default-ew1.yaml b/fast/stages/02-networking-peering/data/subnets/landing/landing-default-ew1.yaml
new file mode 100644
index 000000000..5af68db6d
--- /dev/null
+++ b/fast/stages/02-networking-peering/data/subnets/landing/landing-default-ew1.yaml
@@ -0,0 +1,5 @@
+# skip boilerplate check
+
+region: europe-west1
+ip_cidr_range: 10.128.0.0/24
+description: Default subnet for landing
diff --git a/fast/stages/02-networking-peering/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/02-networking-peering/data/subnets/prod/prod-default-ew1.yaml
new file mode 100644
index 000000000..0052eff95
--- /dev/null
+++ b/fast/stages/02-networking-peering/data/subnets/prod/prod-default-ew1.yaml
@@ -0,0 +1,5 @@
+# skip boilerplate check
+
+region: europe-west1
+ip_cidr_range: 10.128.64.0/24
+description: Default subnet for prod
diff --git a/fast/stages/02-networking-peering/diagram.png b/fast/stages/02-networking-peering/diagram.png
new file mode 100644
index 000000000..3171c5a5c
Binary files /dev/null and b/fast/stages/02-networking-peering/diagram.png differ
diff --git a/fast/stages/02-networking-peering/diagram.svg b/fast/stages/02-networking-peering/diagram.svg
new file mode 100644
index 000000000..0eff39573
--- /dev/null
+++ b/fast/stages/02-networking-peering/diagram.svg
@@ -0,0 +1,1930 @@
+
+
diff --git a/fast/stages/02-networking-peering/dns-dev.tf b/fast/stages/02-networking-peering/dns-dev.tf
new file mode 100644
index 000000000..aad50afc3
--- /dev/null
+++ b/fast/stages/02-networking-peering/dns-dev.tf
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Development spoke DNS zones and peerings setup.
+
+# GCP-specific environment zone
+
+module "dev-dns-private-zone" {
+ source = "../../../modules/dns"
+ project_id = module.dev-spoke-project.project_id
+ type = "private"
+ name = "dev-gcp-example-com"
+ domain = "dev.gcp.example.com."
+ client_networks = [module.landing-vpc.self_link]
+ recordsets = {
+ "A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
+ }
+}
+
+# root zone peering to landing to centralize configuration; remove if unneeded
+
+module "dev-landing-root-dns-peering" {
+ source = "../../../modules/dns"
+ project_id = module.dev-spoke-project.project_id
+ type = "peering"
+ name = "dev-root-dns-peering"
+ domain = "."
+ client_networks = [module.dev-spoke-vpc.self_link]
+ peer_network = module.landing-vpc.self_link
+}
+
+module "dev-reverse-10-dns-peering" {
+ source = "../../../modules/dns"
+ project_id = module.dev-spoke-project.project_id
+ type = "peering"
+ name = "dev-reverse-10-dns-peering"
+ domain = "10.in-addr.arpa."
+ client_networks = [module.dev-spoke-vpc.self_link]
+ peer_network = module.landing-vpc.self_link
+}
diff --git a/fast/stages/02-networking-peering/dns-landing.tf b/fast/stages/02-networking-peering/dns-landing.tf
new file mode 100644
index 000000000..b1d766ab2
--- /dev/null
+++ b/fast/stages/02-networking-peering/dns-landing.tf
@@ -0,0 +1,71 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Landing DNS zones and peerings setup.
+
+# forwarding to on-prem DNS resolvers
+
+module "onprem-example-dns-forwarding" {
+ source = "../../../modules/dns"
+ project_id = module.landing-project.project_id
+ type = "forwarding"
+ name = "example-com"
+ domain = "onprem.example.com."
+ client_networks = [module.landing-vpc.self_link]
+ forwarders = { for ip in var.dns.onprem : ip => null }
+}
+
+module "reverse-10-dns-forwarding" {
+ source = "../../../modules/dns"
+ project_id = module.landing-project.project_id
+ type = "forwarding"
+ name = "root-reverse-10"
+ domain = "10.in-addr.arpa."
+ client_networks = [module.landing-vpc.self_link]
+ forwarders = { for ip in var.dns.onprem : ip => null }
+}
+
+module "gcp-example-dns-private-zone" {
+ source = "../../../modules/dns"
+ project_id = module.landing-project.project_id
+ type = "private"
+ name = "gcp-example-com"
+ domain = "gcp.example.com."
+ client_networks = [module.landing-vpc.self_link]
+ recordsets = {
+ "A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
+ }
+}
+
+# Google API zone to trigger Private Access
+
+module "googleapis-private-zone" {
+ source = "../../../modules/dns"
+ project_id = module.landing-project.project_id
+ type = "private"
+ name = "googleapis-com"
+ domain = "googleapis.com."
+ client_networks = [module.landing-vpc.self_link]
+ recordsets = {
+ "A private" = { type = "A", ttl = 300, records = [
+ "199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
+ ] }
+ "A restricted" = { type = "A", ttl = 300, records = [
+ "199.36.153.4", "199.36.153.5", "199.36.153.6", "199.36.153.7"
+ ] }
+ "CNAME *" = { type = "CNAME", ttl = 300, records = ["private.googleapis.com."] }
+ }
+}
diff --git a/fast/stages/02-networking-peering/dns-prod.tf b/fast/stages/02-networking-peering/dns-prod.tf
new file mode 100644
index 000000000..a4a916b46
--- /dev/null
+++ b/fast/stages/02-networking-peering/dns-prod.tf
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Production spoke DNS zones and peerings setup.
+
+# GCP-specific environment zone
+
+module "prod-dns-private-zone" {
+ source = "../../../modules/dns"
+ project_id = module.prod-spoke-project.project_id
+ type = "private"
+ name = "prod-gcp-example-com"
+ domain = "prod.gcp.example.com."
+ client_networks = [module.landing-vpc.self_link]
+ recordsets = {
+ "A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
+ }
+}
+
+# root zone peering to landing to centralize configuration; remove if unneeded
+
+module "prod-landing-root-dns-peering" {
+ source = "../../../modules/dns"
+ project_id = module.prod-spoke-project.project_id
+ type = "peering"
+ name = "prod-root-dns-peering"
+ domain = "."
+ client_networks = [module.prod-spoke-vpc.self_link]
+ peer_network = module.landing-vpc.self_link
+}
+
+module "prod-reverse-10-dns-peering" {
+ source = "../../../modules/dns"
+ project_id = module.prod-spoke-project.project_id
+ type = "peering"
+ name = "prod-reverse-10-dns-peering"
+ domain = "10.in-addr.arpa."
+ client_networks = [module.prod-spoke-vpc.self_link]
+ peer_network = module.landing-vpc.self_link
+}
diff --git a/fast/stages/02-networking-peering/landing.tf b/fast/stages/02-networking-peering/landing.tf
new file mode 100644
index 000000000..fae959570
--- /dev/null
+++ b/fast/stages/02-networking-peering/landing.tf
@@ -0,0 +1,99 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Landing VPC and related resources.
+
+module "landing-project" {
+ source = "../../../modules/project"
+ billing_account = var.billing_account.id
+ name = "prod-net-landing-0"
+ parent = var.folder_ids.networking-prod
+ prefix = var.prefix
+ service_config = {
+ disable_on_destroy = false
+ disable_dependent_services = false
+ }
+ services = [
+ "compute.googleapis.com",
+ "dns.googleapis.com",
+ "iap.googleapis.com",
+ "networkmanagement.googleapis.com",
+ "stackdriver.googleapis.com"
+ ]
+ shared_vpc_host_config = {
+ enabled = true
+ service_projects = []
+ }
+ iam = {
+ "roles/dns.admin" = [local.service_accounts.project-factory-prod]
+ (local.custom_roles.service_project_network_admin) = [
+ local.service_accounts.project-factory-prod
+ ]
+ }
+}
+
+module "landing-vpc" {
+ source = "../../../modules/net-vpc"
+ project_id = module.landing-project.project_id
+ name = "prod-landing-0"
+ mtu = 1500
+ dns_policy = {
+ inbound = true
+ logging = false
+ outbound = null
+ }
+ # set explicit routes for googleapis in case the default route is deleted
+ routes = {
+ private-googleapis = {
+ dest_range = "199.36.153.8/30"
+ priority = 1000
+ tags = []
+ next_hop_type = "gateway"
+ next_hop = "default-internet-gateway"
+ }
+ restricted-googleapis = {
+ dest_range = "199.36.153.4/30"
+ priority = 1000
+ tags = []
+ next_hop_type = "gateway"
+ next_hop = "default-internet-gateway"
+ }
+ }
+ data_folder = "${var.data_dir}/subnets/landing"
+}
+
+module "landing-firewall" {
+ source = "../../../modules/net-vpc-firewall"
+ project_id = module.landing-project.project_id
+ network = module.landing-vpc.name
+ admin_ranges = []
+ http_source_ranges = []
+ https_source_ranges = []
+ ssh_source_ranges = []
+ data_folder = "${var.data_dir}/firewall-rules/landing"
+ cidr_template_file = "${var.data_dir}/cidrs.yaml"
+}
+
+module "landing-nat-ew1" {
+ source = "../../../modules/net-cloudnat"
+ project_id = module.landing-project.project_id
+ region = "europe-west1"
+ name = "ew1"
+ router_create = true
+ router_name = "prod-nat-ew1"
+ router_network = module.landing-vpc.name
+ router_asn = 4200001024
+}
diff --git a/fast/stages/02-networking-peering/main.tf b/fast/stages/02-networking-peering/main.tf
new file mode 100644
index 000000000..5df6d604e
--- /dev/null
+++ b/fast/stages/02-networking-peering/main.tf
@@ -0,0 +1,58 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Networking folder and hierarchical policy.
+
+locals {
+ custom_roles = coalesce(var.custom_roles, {})
+ l7ilb_subnets = {
+ for env, v in var.l7ilb_subnets : env => [
+ for s in v : merge(s, {
+ active = true
+ name = "${env}-l7ilb-${s.region}"
+ })]
+ }
+ region_trigram = {
+ europe-west1 = "ew1"
+ europe-west3 = "ew3"
+ }
+ stage3_sas_delegated_grants = [
+ "roles/composer.sharedVpcAgent",
+ "roles/compute.networkUser",
+ "roles/container.hostServiceAgentUser",
+ "roles/vpcaccess.user",
+ ]
+ service_accounts = {
+ for k, v in coalesce(var.service_accounts, {}) : k => "serviceAccount:${v}"
+ }
+}
+
+module "folder" {
+ source = "../../../modules/folder"
+ parent = "organizations/${var.organization.id}"
+ name = "Networking"
+ folder_create = var.folder_ids.networking == null
+ id = var.folder_ids.networking
+ firewall_policy_factory = {
+ cidr_file = "${var.data_dir}/cidrs.yaml"
+ policy_name = null
+ rules_file = "${var.data_dir}/hierarchical-policy-rules.yaml"
+ }
+ firewall_policy_association = {
+ factory-policy = "factory"
+ }
+}
+
diff --git a/fast/stages/02-networking-peering/monitoring.tf b/fast/stages/02-networking-peering/monitoring.tf
new file mode 100644
index 000000000..7b8b70c51
--- /dev/null
+++ b/fast/stages/02-networking-peering/monitoring.tf
@@ -0,0 +1,32 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Network monitoring dashboards.
+
+locals {
+ dashboard_path = "${var.data_dir}/dashboards"
+ dashboard_files = fileset(local.dashboard_path, "*.json")
+ dashboards = {
+ for filename in local.dashboard_files :
+ filename => "${local.dashboard_path}/${filename}"
+ }
+}
+
+resource "google_monitoring_dashboard" "dashboard" {
+ for_each = local.dashboards
+ project = module.landing-project.project_id
+ dashboard_json = file(each.value)
+}
diff --git a/fast/stages/02-networking-peering/outputs.tf b/fast/stages/02-networking-peering/outputs.tf
new file mode 100644
index 000000000..3fe18d657
--- /dev/null
+++ b/fast/stages/02-networking-peering/outputs.tf
@@ -0,0 +1,91 @@
+/**
+ * Copyright 2022 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.
+ */
+
+locals {
+ host_project_ids = {
+ dev-spoke-0 = module.dev-spoke-project.project_id
+ prod-landing = module.landing-project.project_id
+ prod-spoke-0 = module.prod-spoke-project.project_id
+ }
+ host_project_numbers = {
+ dev-spoke-0 = module.dev-spoke-project.number
+ prod-landing = module.landing-project.number
+ prod-spoke-0 = module.prod-spoke-project.number
+ }
+ subnet_self_links = {
+ prod-landing = module.landing-vpc.subnet_self_links
+ dev-spoke-0 = module.dev-spoke-vpc.subnet_self_links
+ prod-spoke-0 = module.prod-spoke-vpc.subnet_self_links
+ }
+ tfvars = {
+ host_project_ids = local.host_project_ids
+ host_project_numbers = local.host_project_numbers
+ subnet_self_links = local.subnet_self_links
+ vpc_self_links = local.vpc_self_links
+ }
+ vpc_self_links = {
+ prod-landing = module.landing-vpc.self_link
+ dev-spoke-0 = module.dev-spoke-vpc.self_link
+ prod-spoke-0 = module.prod-spoke-vpc.self_link
+ }
+}
+
+# optionally generate tfvars file for subsequent stages
+
+resource "local_file" "tfvars" {
+ for_each = var.outputs_location == null ? {} : { 1 = 1 }
+ file_permission = "0644"
+ filename = "${pathexpand(var.outputs_location)}/tfvars/02-networking.auto.tfvars.json"
+ content = jsonencode(local.tfvars)
+}
+
+# outputs
+
+output "cloud_dns_inbound_policy" {
+ description = "IP Addresses for Cloud DNS inbound policy."
+ value = [for s in module.landing-vpc.subnets : cidrhost(s.ip_cidr_range, 2)]
+}
+
+output "host_project_ids" {
+ description = "Network project ids."
+ value = local.host_project_ids
+}
+
+output "host_project_numbers" {
+ description = "Network project numbers."
+ value = local.host_project_numbers
+}
+
+output "shared_vpc_self_links" {
+ description = "Shared VPC host projects."
+ value = local.vpc_self_links
+}
+
+output "vpn_gateway_endpoints" {
+ description = "External IP Addresses for the GCP VPN gateways."
+ value = local.enable_onprem_vpn == false ? null : {
+ onprem-ew1 = {
+ for v in module.landing-to-onprem-ew1-vpn[0].gateway.vpn_interfaces :
+ v.id => v.ip_address
+ }
+ }
+}
+
+output "tfvars" {
+ description = "Terraform variables file for the following stages."
+ sensitive = true
+ value = local.tfvars
+}
diff --git a/fast/stages/02-networking-peering/peerings.tf b/fast/stages/02-networking-peering/peerings.tf
new file mode 100644
index 000000000..13dbf63d1
--- /dev/null
+++ b/fast/stages/02-networking-peering/peerings.tf
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2022 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 "peering-dev" {
+ source = "../../../modules/net-vpc-peering"
+ prefix = "dev-peering-0"
+ local_network = module.dev-spoke-vpc.self_link
+ peer_network = module.landing-vpc.self_link
+ export_local_custom_routes = try(
+ var.peering_configs.dev.export_local_custom_routes, null
+ )
+ export_peer_custom_routes = try(
+ var.peering_configs.dev.export_peer_custom_routes, null
+ )
+}
+
+module "peering-prod" {
+ source = "../../../modules/net-vpc-peering"
+ prefix = "prod-peering-0"
+ local_network = module.prod-spoke-vpc.self_link
+ peer_network = module.landing-vpc.self_link
+ depends_on = [module.peering-dev]
+ export_local_custom_routes = try(
+ var.peering_configs.prod.export_local_custom_routes, null
+ )
+ export_peer_custom_routes = try(
+ var.peering_configs.prod.export_peer_custom_routes, null
+ )
+}
+
diff --git a/fast/stages/02-networking-peering/spoke-dev.tf b/fast/stages/02-networking-peering/spoke-dev.tf
new file mode 100644
index 000000000..d62949afc
--- /dev/null
+++ b/fast/stages/02-networking-peering/spoke-dev.tf
@@ -0,0 +1,114 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Dev spoke VPC and related resources.
+
+module "dev-spoke-project" {
+ source = "../../../modules/project"
+ billing_account = var.billing_account.id
+ name = "dev-net-spoke-0"
+ parent = var.folder_ids.networking-dev
+ prefix = var.prefix
+ service_config = {
+ disable_on_destroy = false
+ disable_dependent_services = false
+ }
+ services = [
+ "container.googleapis.com",
+ "compute.googleapis.com",
+ "dns.googleapis.com",
+ "iap.googleapis.com",
+ "networkmanagement.googleapis.com",
+ "servicenetworking.googleapis.com",
+ ]
+ shared_vpc_host_config = {
+ enabled = true
+ service_projects = []
+ }
+ metric_scopes = [module.landing-project.project_id]
+ iam = {
+ "roles/dns.admin" = [local.service_accounts.project-factory-dev]
+ }
+}
+
+module "dev-spoke-vpc" {
+ source = "../../../modules/net-vpc"
+ project_id = module.dev-spoke-project.project_id
+ name = "dev-spoke-0"
+ mtu = 1500
+ data_folder = "${var.data_dir}/subnets/dev"
+ psa_ranges = var.psa_ranges.dev
+ subnets_l7ilb = local.l7ilb_subnets.dev
+ # set explicit routes for googleapis in case the default route is deleted
+ routes = {
+ private-googleapis = {
+ dest_range = "199.36.153.8/30"
+ priority = 1000
+ tags = []
+ next_hop_type = "gateway"
+ next_hop = "default-internet-gateway"
+ }
+ restricted-googleapis = {
+ dest_range = "199.36.153.4/30"
+ priority = 1000
+ tags = []
+ next_hop_type = "gateway"
+ next_hop = "default-internet-gateway"
+ }
+ }
+}
+
+module "dev-spoke-firewall" {
+ source = "../../../modules/net-vpc-firewall"
+ project_id = module.dev-spoke-project.project_id
+ network = module.dev-spoke-vpc.name
+ admin_ranges = []
+ http_source_ranges = []
+ https_source_ranges = []
+ ssh_source_ranges = []
+ data_folder = "${var.data_dir}/firewall-rules/dev"
+ cidr_template_file = "${var.data_dir}/cidrs.yaml"
+}
+
+module "dev-spoke-cloudnat" {
+ for_each = toset(values(module.dev-spoke-vpc.subnet_regions))
+ source = "../../../modules/net-cloudnat"
+ project_id = module.dev-spoke-project.project_id
+ region = each.value
+ name = "dev-nat-${local.region_trigram[each.value]}"
+ router_create = true
+ router_network = module.dev-spoke-vpc.name
+ router_asn = 4200001024
+ logging_filter = "ERRORS_ONLY"
+}
+
+# Create delegated grants for stage3 service accounts
+resource "google_project_iam_binding" "dev_spoke_project_iam_delegated" {
+ project = module.dev-spoke-project.project_id
+ role = "roles/resourcemanager.projectIamAdmin"
+ members = [
+ local.service_accounts.data-platform-dev,
+ local.service_accounts.project-factory-dev,
+ ]
+ condition {
+ title = "dev_stage3_sa_delegated_grants"
+ description = "Development host project delegated grants."
+ expression = format(
+ "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])",
+ join(",", formatlist("'%s'", local.stage3_sas_delegated_grants))
+ )
+ }
+}
diff --git a/fast/stages/02-networking-peering/spoke-prod.tf b/fast/stages/02-networking-peering/spoke-prod.tf
new file mode 100644
index 000000000..001bab75d
--- /dev/null
+++ b/fast/stages/02-networking-peering/spoke-prod.tf
@@ -0,0 +1,114 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Production spoke VPC and related resources.
+
+module "prod-spoke-project" {
+ source = "../../../modules/project"
+ billing_account = var.billing_account.id
+ name = "prod-net-spoke-0"
+ parent = var.folder_ids.networking-prod
+ prefix = var.prefix
+ service_config = {
+ disable_on_destroy = false
+ disable_dependent_services = false
+ }
+ services = [
+ "container.googleapis.com",
+ "compute.googleapis.com",
+ "dns.googleapis.com",
+ "iap.googleapis.com",
+ "networkmanagement.googleapis.com",
+ "servicenetworking.googleapis.com",
+ ]
+ shared_vpc_host_config = {
+ enabled = true
+ service_projects = []
+ }
+ metric_scopes = [module.landing-project.project_id]
+ iam = {
+ "roles/dns.admin" = [local.service_accounts.project-factory-prod]
+ }
+}
+
+module "prod-spoke-vpc" {
+ source = "../../../modules/net-vpc"
+ project_id = module.prod-spoke-project.project_id
+ name = "prod-spoke-0"
+ mtu = 1500
+ data_folder = "${var.data_dir}/subnets/prod"
+ psa_ranges = var.psa_ranges.prod
+ subnets_l7ilb = local.l7ilb_subnets.prod
+ # set explicit routes for googleapis in case the default route is deleted
+ routes = {
+ private-googleapis = {
+ dest_range = "199.36.153.8/30"
+ priority = 1000
+ tags = []
+ next_hop_type = "gateway"
+ next_hop = "default-internet-gateway"
+ }
+ restricted-googleapis = {
+ dest_range = "199.36.153.4/30"
+ priority = 1000
+ tags = []
+ next_hop_type = "gateway"
+ next_hop = "default-internet-gateway"
+ }
+ }
+}
+
+module "prod-spoke-firewall" {
+ source = "../../../modules/net-vpc-firewall"
+ project_id = module.prod-spoke-project.project_id
+ network = module.prod-spoke-vpc.name
+ admin_ranges = []
+ http_source_ranges = []
+ https_source_ranges = []
+ ssh_source_ranges = []
+ data_folder = "${var.data_dir}/firewall-rules/prod"
+ cidr_template_file = "${var.data_dir}/cidrs.yaml"
+}
+
+module "prod-spoke-cloudnat" {
+ for_each = toset(values(module.prod-spoke-vpc.subnet_regions))
+ source = "../../../modules/net-cloudnat"
+ project_id = module.prod-spoke-project.project_id
+ region = each.value
+ name = "prod-nat-${local.region_trigram[each.value]}"
+ router_create = true
+ router_network = module.prod-spoke-vpc.name
+ router_asn = 4200001024
+ logging_filter = "ERRORS_ONLY"
+}
+
+# Create delegated grants for stage3 service accounts
+resource "google_project_iam_binding" "prod_spoke_project_iam_delegated" {
+ project = module.prod-spoke-project.project_id
+ role = "roles/resourcemanager.projectIamAdmin"
+ members = [
+ local.service_accounts.data-platform-prod,
+ local.service_accounts.project-factory-prod,
+ ]
+ condition {
+ title = "prod_stage3_sa_delegated_grants"
+ description = "Production host project delegated grants."
+ expression = format(
+ "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])",
+ join(",", formatlist("'%s'", local.stage3_sas_delegated_grants))
+ )
+ }
+}
diff --git a/fast/stages/02-networking-peering/test-resources.tf b/fast/stages/02-networking-peering/test-resources.tf
new file mode 100644
index 000000000..8139e7551
--- /dev/null
+++ b/fast/stages/02-networking-peering/test-resources.tf
@@ -0,0 +1,100 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description temporary instances for testing
+
+# module "test-vm-landing-0" {
+# source = "../../../modules/compute-vm"
+# project_id = module.landing-project.project_id
+# zone = "europe-west1-b"
+# name = "test-vm-0"
+# network_interfaces = [{
+# network = module.landing-vpc.self_link
+# subnetwork = module.landing-vpc.subnet_self_links["europe-west1/landing-default-ew1"]
+# alias_ips = {}
+# nat = false
+# addresses = null
+# }]
+# tags = ["ssh"]
+# service_account_create = true
+# boot_disk = {
+# image = "projects/debian-cloud/global/images/family/debian-10"
+# type = "pd-balanced"
+# size = 10
+# }
+# metadata = {
+# startup-script = <net-cloudnat · net-vpc · net-vpc-firewall · project | google_project_iam_binding |
| [spoke-prod.tf](./spoke-prod.tf) | Production spoke VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | google_project_iam_binding |
| [test-resources.tf](./test-resources.tf) | temporary instances for testing | compute-vm | |
+| [variables-vpn.tf](./variables-vpn.tf) | None | | |
| [variables.tf](./variables.tf) | Module variables. | | |
| [vpn-onprem.tf](./vpn-onprem.tf) | VPN between landing and onprem. | net-vpn-ha | |
| [vpn-spoke-dev.tf](./vpn-spoke-dev.tf) | VPN between landing and development spoke. | net-vpn-ha | |
@@ -321,10 +322,11 @@ DNS configurations are centralised in the `dns.tf` file. Spokes delegate DNS res
| [l7ilb_subnets](variables.tf#L76) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | |
| [outputs_location](variables.tf#L104) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | |
| [psa_ranges](variables.tf#L121) | IP ranges used for Private Service Access (e.g. CloudSQL). | map(map(string)) | | {…} | |
-| [router_configs](variables.tf#L136) | Configurations for CRs and onprem routers. | map(object({…})) | | {…} | |
-| [service_accounts](variables.tf#L160) | Automation service accounts in name => email format. | object({…}) | | null | 01-resman |
-| [vpn_onprem_configs](variables.tf#L172) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | |
-| [vpn_spoke_configs](variables.tf#L228) | VPN gateway configuration for spokes. | map(object({…})) | | {…} | |
+| [router_onprem_configs](variables.tf#L136) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | |
+| [router_spoke_configs](variables-vpn.tf#L18) | Configurations for routers used for internal connectivity. | map(object({…})) | | {…} | |
+| [service_accounts](variables.tf#L154) | Automation service accounts in name => email format. | object({…}) | | null | 01-resman |
+| [vpn_onprem_configs](variables.tf#L166) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | |
+| [vpn_spoke_configs](variables-vpn.tf#L37) | VPN gateway configuration for spokes. | map(object({…})) | | {…} | |
## Outputs
diff --git a/fast/stages/02-networking-vpn/data/firewall-rules/dev/rules.yaml b/fast/stages/02-networking-vpn/data/firewall-rules/dev/rules.yaml
index f2d614a59..d4df8cdc3 100644
--- a/fast/stages/02-networking-vpn/data/firewall-rules/dev/rules.yaml
+++ b/fast/stages/02-networking-vpn/data/firewall-rules/dev/rules.yaml
@@ -5,7 +5,7 @@ ingress-allow-composer-nodes:
direction: INGRESS
action: allow
sources: []
- ranges: []
+ ranges: ["0.0.0.0/0"]
targets:
- composer-worker
use_service_accounts: false
@@ -18,7 +18,7 @@ ingress-allow-dataflow-load:
direction: INGRESS
action: allow
sources: []
- ranges: []
+ ranges: ["0.0.0.0/0"]
targets:
- dataflow
use_service_accounts: false
diff --git a/fast/stages/02-networking-vpn/dns-dev.tf b/fast/stages/02-networking-vpn/dns-dev.tf
index 3c81a93fc..aad50afc3 100644
--- a/fast/stages/02-networking-vpn/dns-dev.tf
+++ b/fast/stages/02-networking-vpn/dns-dev.tf
@@ -20,11 +20,11 @@
module "dev-dns-private-zone" {
source = "../../../modules/dns"
- project_id = module.landing-project.project_id
+ project_id = module.dev-spoke-project.project_id
type = "private"
name = "dev-gcp-example-com"
domain = "dev.gcp.example.com."
- client_networks = [module.dev-spoke-vpc.self_link]
+ client_networks = [module.landing-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
}
diff --git a/fast/stages/02-networking-vpn/dns-landing.tf b/fast/stages/02-networking-vpn/dns-landing.tf
index 611410b5e..b1d766ab2 100644
--- a/fast/stages/02-networking-vpn/dns-landing.tf
+++ b/fast/stages/02-networking-vpn/dns-landing.tf
@@ -50,28 +50,6 @@ module "gcp-example-dns-private-zone" {
}
}
-# GCP-specific DNS zones peered to the environment spoke that holds the config
-
-module "prod-gcp-example-dns-peering" {
- source = "../../../modules/dns"
- project_id = module.landing-project.project_id
- type = "peering"
- name = "prod-root-dns-peering"
- domain = "prod.gcp.example.com."
- client_networks = [module.landing-vpc.self_link]
- peer_network = module.prod-spoke-vpc.self_link
-}
-
-module "dev-gcp-example-dns-peering" {
- source = "../../../modules/dns"
- project_id = module.landing-project.project_id
- type = "peering"
- name = "dev-root-dns-peering"
- domain = "dev.gcp.example.com."
- client_networks = [module.landing-vpc.self_link]
- peer_network = module.dev-spoke-vpc.self_link
-}
-
# Google API zone to trigger Private Access
module "googleapis-private-zone" {
diff --git a/fast/stages/02-networking-vpn/dns-prod.tf b/fast/stages/02-networking-vpn/dns-prod.tf
index 22977348b..a4a916b46 100644
--- a/fast/stages/02-networking-vpn/dns-prod.tf
+++ b/fast/stages/02-networking-vpn/dns-prod.tf
@@ -20,11 +20,11 @@
module "prod-dns-private-zone" {
source = "../../../modules/dns"
- project_id = module.landing-project.project_id
+ project_id = module.prod-spoke-project.project_id
type = "private"
name = "prod-gcp-example-com"
domain = "prod.gcp.example.com."
- client_networks = [module.prod-spoke-vpc.self_link]
+ client_networks = [module.landing-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
}
diff --git a/fast/stages/02-networking-vpn/main.tf b/fast/stages/02-networking-vpn/main.tf
index 964b7dfc3..5df6d604e 100644
--- a/fast/stages/02-networking-vpn/main.tf
+++ b/fast/stages/02-networking-vpn/main.tf
@@ -17,19 +17,6 @@
# tfdoc:file:description Networking folder and hierarchical policy.
locals {
- # define the structures used for BGP peers in the VPN resources
- bgp_peer_options = {
- for k, v in var.vpn_spoke_configs :
- k => v.adv == null ? null : {
- advertise_groups = []
- advertise_ip_ranges = {
- for adv in(v.adv == null ? [] : v.adv.custom) :
- var.custom_adv[adv] => adv
- }
- advertise_mode = try(v.adv.default, false) ? "DEFAULT" : "CUSTOM"
- route_priority = null
- }
- }
custom_roles = coalesce(var.custom_roles, {})
l7ilb_subnets = {
for env, v in var.l7ilb_subnets : env => [
diff --git a/fast/stages/02-networking-vpn/outputs.tf b/fast/stages/02-networking-vpn/outputs.tf
index 78f69fc2d..3fe18d657 100644
--- a/fast/stages/02-networking-vpn/outputs.tf
+++ b/fast/stages/02-networking-vpn/outputs.tf
@@ -76,9 +76,9 @@ output "shared_vpc_self_links" {
output "vpn_gateway_endpoints" {
description = "External IP Addresses for the GCP VPN gateways."
- value = {
+ value = local.enable_onprem_vpn == false ? null : {
onprem-ew1 = {
- for v in module.landing-to-onprem-ew1-vpn.gateway.vpn_interfaces :
+ for v in module.landing-to-onprem-ew1-vpn[0].gateway.vpn_interfaces :
v.id => v.ip_address
}
}
diff --git a/fast/stages/02-networking-vpn/variables-vpn.tf b/fast/stages/02-networking-vpn/variables-vpn.tf
new file mode 100644
index 000000000..1732a3a1e
--- /dev/null
+++ b/fast/stages/02-networking-vpn/variables-vpn.tf
@@ -0,0 +1,88 @@
+/**
+ * Copyright 2022 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.
+ */
+
+
+variable "router_spoke_configs" {
+ description = "Configurations for routers used for internal connectivity."
+ type = map(object({
+ adv = object({
+ custom = list(string)
+ default = bool
+ })
+ asn = number
+ }))
+ default = {
+ landing-ew1 = { asn = "64512", adv = null }
+ landing-ew4 = { asn = "64512", adv = null }
+ spoke-dev-ew1 = { asn = "64513", adv = null }
+ spoke-dev-ew4 = { asn = "64513", adv = null }
+ spoke-prod-ew1 = { asn = "64514", adv = null }
+ spoke-prod-ew4 = { asn = "64514", adv = null }
+ }
+}
+
+variable "vpn_spoke_configs" {
+ description = "VPN gateway configuration for spokes."
+ type = map(object({
+ adv = object({
+ default = bool
+ custom = list(string)
+ })
+ session_range = string
+ }))
+ default = {
+ landing-ew1 = {
+ adv = {
+ default = false
+ custom = ["rfc_1918_10", "rfc_1918_172", "rfc_1918_192"]
+ }
+ # values for the landing router are pulled from the spoke range
+ session_range = null
+ }
+ landing-ew4 = {
+ adv = {
+ default = false
+ custom = ["rfc_1918_10", "rfc_1918_172", "rfc_1918_192"]
+ }
+ # values for the landing router are pulled from the spoke range
+ session_range = null
+ }
+ dev-ew1 = {
+ adv = {
+ default = false
+ custom = ["gcp_dev"]
+ }
+ # resize according to required number of tunnels
+ session_range = "169.254.0.0/27"
+ }
+ prod-ew1 = {
+ adv = {
+ default = false
+ custom = ["gcp_prod"]
+ }
+ # resize according to required number of tunnels
+ session_range = "169.254.0.64/27"
+ }
+ prod-ew4 = {
+ adv = {
+ default = false
+ custom = ["gcp_prod"]
+ }
+ # resize according to required number of tunnels
+ session_range = "169.254.0.96/27"
+ }
+ }
+}
diff --git a/fast/stages/02-networking-vpn/variables.tf b/fast/stages/02-networking-vpn/variables.tf
index 8f8788f9d..426dc718f 100644
--- a/fast/stages/02-networking-vpn/variables.tf
+++ b/fast/stages/02-networking-vpn/variables.tf
@@ -133,8 +133,8 @@ variable "psa_ranges" {
}
}
-variable "router_configs" {
- description = "Configurations for CRs and onprem routers."
+variable "router_onprem_configs" {
+ description = "Configurations for routers used for onprem connectivity."
type = map(object({
adv = object({
custom = list(string)
@@ -143,17 +143,11 @@ variable "router_configs" {
asn = number
}))
default = {
- onprem-ew1 = {
- asn = "65534"
+ landing-ew1 = {
+ asn = "65533"
adv = null
# adv = { default = false, custom = [] }
}
- landing-ew1 = { asn = "64512", adv = null }
- landing-ew4 = { asn = "64512", adv = null }
- spoke-dev-ew1 = { asn = "64513", adv = null }
- spoke-dev-ew4 = { asn = "64513", adv = null }
- spoke-prod-ew1 = { asn = "64514", adv = null }
- spoke-prod-ew4 = { asn = "64514", adv = null }
}
}
@@ -224,56 +218,3 @@ variable "vpn_onprem_configs" {
}
}
}
-
-variable "vpn_spoke_configs" {
- description = "VPN gateway configuration for spokes."
- type = map(object({
- adv = object({
- default = bool
- custom = list(string)
- })
- session_range = string
- }))
- default = {
- landing-ew1 = {
- adv = {
- default = false
- custom = ["rfc_1918_10", "rfc_1918_172", "rfc_1918_192"]
- }
- # values for the landing router are pulled from the spoke range
- session_range = null
- }
- landing-ew4 = {
- adv = {
- default = false
- custom = ["rfc_1918_10", "rfc_1918_172", "rfc_1918_192"]
- }
- # values for the landing router are pulled from the spoke range
- session_range = null
- }
- dev-ew1 = {
- adv = {
- default = false
- custom = ["gcp_dev"]
- }
- # resize according to required number of tunnels
- session_range = "169.254.0.0/27"
- }
- prod-ew1 = {
- adv = {
- default = false
- custom = ["gcp_prod"]
- }
- # resize according to required number of tunnels
- session_range = "169.254.0.64/27"
- }
- prod-ew4 = {
- adv = {
- default = false
- custom = ["gcp_prod"]
- }
- # resize according to required number of tunnels
- session_range = "169.254.0.96/27"
- }
- }
-}
diff --git a/fast/stages/02-networking-vpn/vpn-onprem.tf b/fast/stages/02-networking-vpn/vpn-onprem.tf
index 859e1a770..48cad54b3 100644
--- a/fast/stages/02-networking-vpn/vpn-onprem.tf
+++ b/fast/stages/02-networking-vpn/vpn-onprem.tf
@@ -17,7 +17,8 @@
# tfdoc:file:description VPN between landing and onprem.
locals {
- bgp_peer_options_onprem = {
+ enable_onprem_vpn = var.vpn_onprem_configs != null
+ bgp_peer_options_onprem = local.enable_onprem_vpn == false ? null : {
for k, v in var.vpn_onprem_configs :
k => v.adv == null ? null : {
advertise_groups = []
@@ -32,6 +33,7 @@ locals {
}
module "landing-to-onprem-ew1-vpn" {
+ count = local.enable_onprem_vpn ? 1 : 0
source = "../../../modules/net-vpn-ha"
project_id = module.landing-project.project_id
network = module.landing-vpc.self_link
@@ -39,7 +41,7 @@ module "landing-to-onprem-ew1-vpn" {
name = "vpn-to-onprem-ew1"
router_create = true
router_name = "landing-onprem-vpn-ew1"
- router_asn = var.router_configs.landing-ew1.asn
+ router_asn = var.router_onprem_configs.landing-ew1.asn
peer_external_gateway = var.vpn_onprem_configs.landing-ew1.peer_external_gateway
tunnels = {
for t in var.vpn_onprem_configs.landing-ew1.tunnels :
diff --git a/fast/stages/02-networking-vpn/vpn-spoke-dev.tf b/fast/stages/02-networking-vpn/vpn-spoke-dev.tf
index edfe4000b..01d43d479 100644
--- a/fast/stages/02-networking-vpn/vpn-spoke-dev.tf
+++ b/fast/stages/02-networking-vpn/vpn-spoke-dev.tf
@@ -16,6 +16,24 @@
# tfdoc:file:description VPN between landing and development spoke.
+locals {
+ # define the structures used for BGP peers in the VPN resources
+ vpn_spoke_bgp_peer_options = {
+ for k, v in var.vpn_spoke_configs :
+ k => v.adv == null ? null : {
+ advertise_groups = []
+ advertise_ip_ranges = {
+ for adv in(v.adv == null ? [] : v.adv.custom) :
+ var.custom_adv[adv] => adv
+ }
+ advertise_mode = try(v.adv.default, false) ? "DEFAULT" : "CUSTOM"
+ route_priority = null
+ }
+ }
+}
+
+# development spoke
+
module "landing-to-dev-ew1-vpn" {
source = "../../../modules/net-vpn-ha"
project_id = module.landing-project.project_id
@@ -25,15 +43,17 @@ module "landing-to-dev-ew1-vpn" {
# The router used for this VPN is managed in vpn-prod.tf
router_create = false
router_name = "landing-vpn-ew1"
- router_asn = var.router_configs.landing-ew1.asn
+ router_asn = var.router_spoke_configs.landing-ew1.asn
peer_gcp_gateway = module.dev-to-landing-ew1-vpn.self_link
tunnels = { for t in range(2) : "tunnel-${t}" => {
bgp_peer = {
address = cidrhost(var.vpn_spoke_configs.dev-ew1.session_range, 1 + (t * 4))
- asn = var.router_configs.spoke-dev-ew1.asn
+ asn = var.router_spoke_configs.spoke-dev-ew1.asn
}
- bgp_peer_options = local.bgp_peer_options["landing-ew1"]
- bgp_session_range = "${cidrhost(var.vpn_spoke_configs.dev-ew1.session_range, 2 + (t * 4))}/30"
+ bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-ew1
+ bgp_session_range = "${cidrhost(
+ var.vpn_spoke_configs.dev-ew1.session_range, 2 + (t * 4)
+ )}/30"
ike_version = 2
peer_external_gateway_interface = null
router = null
@@ -54,15 +74,17 @@ module "dev-to-landing-ew1-vpn" {
name = "vpn-to-landing-ew1"
router_create = true
router_name = "dev-spoke-vpn-ew1"
- router_asn = var.router_configs.spoke-dev-ew1.asn
+ router_asn = var.router_spoke_configs.spoke-dev-ew1.asn
peer_gcp_gateway = module.landing-to-dev-ew1-vpn.self_link
tunnels = { for t in range(2) : "tunnel-${t}" => {
bgp_peer = {
address = cidrhost(var.vpn_spoke_configs.dev-ew1.session_range, 2 + (t * 4))
- asn = var.router_configs.landing-ew1.asn
+ asn = var.router_spoke_configs.landing-ew1.asn
}
- bgp_peer_options = local.bgp_peer_options["dev-ew1"]
- bgp_session_range = "${cidrhost(var.vpn_spoke_configs.dev-ew1.session_range, 1 + (t * 4))}/30"
+ bgp_peer_options = local.vpn_spoke_bgp_peer_options.dev-ew1
+ bgp_session_range = "${cidrhost(
+ var.vpn_spoke_configs.dev-ew1.session_range, 1 + (t * 4)
+ )}/30"
ike_version = 2
peer_external_gateway_interface = null
router = null
diff --git a/fast/stages/02-networking-vpn/vpn-spoke-prod.tf b/fast/stages/02-networking-vpn/vpn-spoke-prod.tf
index a6d1ea166..ff635194c 100644
--- a/fast/stages/02-networking-vpn/vpn-spoke-prod.tf
+++ b/fast/stages/02-networking-vpn/vpn-spoke-prod.tf
@@ -16,6 +16,8 @@
# tfdoc:file:description VPN between landing and production spoke.
+# local.vpn_spoke_bgp_peer_options is defined in the dev VPN file
+
module "landing-to-prod-ew1-vpn" {
source = "../../../modules/net-vpn-ha"
project_id = module.landing-project.project_id
@@ -24,15 +26,19 @@ module "landing-to-prod-ew1-vpn" {
name = "vpn-to-prod-ew1"
router_create = true
router_name = "landing-vpn-ew1"
- router_asn = var.router_configs.landing-ew1.asn
+ router_asn = var.router_spoke_configs.landing-ew1.asn
peer_gcp_gateway = module.prod-to-landing-ew1-vpn.self_link
tunnels = { for t in range(2) : "tunnel-${t}" => {
bgp_peer = {
- address = cidrhost(var.vpn_spoke_configs.prod-ew1.session_range, 1 + (t * 4))
- asn = var.router_configs.spoke-prod-ew1.asn
+ address = cidrhost(
+ var.vpn_spoke_configs.prod-ew1.session_range, 1 + (t * 4)
+ )
+ asn = var.router_spoke_configs.spoke-prod-ew1.asn
}
- bgp_peer_options = local.bgp_peer_options["landing-ew1"]
- bgp_session_range = "${cidrhost(var.vpn_spoke_configs.prod-ew1.session_range, 2 + (t * 4))}/30"
+ bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-ew1
+ bgp_session_range = "${cidrhost(
+ var.vpn_spoke_configs.prod-ew1.session_range, 2 + (t * 4)
+ )}/30"
ike_version = 2
peer_external_gateway_interface = null
router = null
@@ -50,15 +56,19 @@ module "prod-to-landing-ew1-vpn" {
name = "vpn-to-landing-ew1"
router_create = true
router_name = "prod-spoke-vpn-ew1"
- router_asn = var.router_configs.spoke-prod-ew1.asn
+ router_asn = var.router_spoke_configs.spoke-prod-ew1.asn
peer_gcp_gateway = module.landing-to-prod-ew1-vpn.self_link
tunnels = { for t in range(2) : "tunnel-${t}" => {
bgp_peer = {
- address = cidrhost(var.vpn_spoke_configs.prod-ew1.session_range, 2 + (t * 4))
- asn = var.router_configs.landing-ew1.asn
+ address = cidrhost(
+ var.vpn_spoke_configs.prod-ew1.session_range, 2 + (t * 4)
+ )
+ asn = var.router_spoke_configs.landing-ew1.asn
}
- bgp_peer_options = local.bgp_peer_options["prod-ew1"]
- bgp_session_range = "${cidrhost(var.vpn_spoke_configs.prod-ew1.session_range, 1 + (t * 4))}/30"
+ bgp_peer_options = local.vpn_spoke_bgp_peer_options.prod-ew1
+ bgp_session_range = "${cidrhost(
+ var.vpn_spoke_configs.prod-ew1.session_range, 1 + (t * 4)
+ )}/30"
ike_version = 2
peer_external_gateway_interface = null
router = null
@@ -76,15 +86,19 @@ module "landing-to-prod-ew4-vpn" {
name = "vpn-to-prod-ew4"
router_create = true
router_name = "landing-vpn-ew4"
- router_asn = var.router_configs.landing-ew4.asn
+ router_asn = var.router_spoke_configs.landing-ew4.asn
peer_gcp_gateway = module.prod-to-landing-ew4-vpn.self_link
tunnels = { for t in range(2) : "tunnel-${t}" => {
bgp_peer = {
- address = cidrhost(var.vpn_spoke_configs.prod-ew4.session_range, 1 + (t * 4))
- asn = var.router_configs.spoke-prod-ew4.asn
+ address = cidrhost(
+ var.vpn_spoke_configs.prod-ew4.session_range, 1 + (t * 4)
+ )
+ asn = var.router_spoke_configs.spoke-prod-ew4.asn
}
- bgp_peer_options = local.bgp_peer_options["landing-ew4"]
- bgp_session_range = "${cidrhost(var.vpn_spoke_configs.prod-ew4.session_range, 2 + (t * 4))}/30"
+ bgp_peer_options = local.vpn_spoke_bgp_peer_options.landing-ew4
+ bgp_session_range = "${cidrhost(
+ var.vpn_spoke_configs.prod-ew4.session_range, 2 + (t * 4)
+ )}/30"
ike_version = 2
peer_external_gateway_interface = null
router = null
@@ -102,15 +116,19 @@ module "prod-to-landing-ew4-vpn" {
name = "vpn-to-landing-ew4"
router_create = true
router_name = "prod-spoke-vpn-ew4"
- router_asn = var.router_configs.spoke-prod-ew4.asn
+ router_asn = var.router_spoke_configs.spoke-prod-ew4.asn
peer_gcp_gateway = module.landing-to-prod-ew4-vpn.self_link
tunnels = { for t in range(2) : "tunnel-${t}" => {
bgp_peer = {
- address = cidrhost(var.vpn_spoke_configs.prod-ew4.session_range, 2 + (t * 4))
- asn = var.router_configs.landing-ew4.asn
+ address = cidrhost(
+ var.vpn_spoke_configs.prod-ew4.session_range, 2 + (t * 4)
+ )
+ asn = var.router_spoke_configs.landing-ew4.asn
}
- bgp_peer_options = local.bgp_peer_options["prod-ew4"]
- bgp_session_range = "${cidrhost(var.vpn_spoke_configs.prod-ew4.session_range, 1 + (t * 4))}/30"
+ bgp_peer_options = local.vpn_spoke_bgp_peer_options.prod-ew4
+ bgp_session_range = "${cidrhost(
+ var.vpn_spoke_configs.prod-ew4.session_range, 1 + (t * 4)
+ )}/30"
ike_version = 2
peer_external_gateway_interface = null
router = null
diff --git a/modules/cloud-config-container/coredns/README.md b/modules/cloud-config-container/coredns/README.md
index 658f67345..09cc44916 100644
--- a/modules/cloud-config-container/coredns/README.md
+++ b/modules/cloud-config-container/coredns/README.md
@@ -79,11 +79,14 @@ module "cos-coredns" {
| [coredns_config](variables.tf#L29) | CoreDNS configuration path, if null default will be used. | string | | null |
| [file_defaults](variables.tf#L35) | Default owner and permissions for files. | object({…}) | | {…} |
| [files](variables.tf#L47) | Map of extra files to create on the instance, path as key. Owner and permissions will use defaults if null. | map(object({…})) | | {} |
+| [test_instance](variables-instance.tf#L17) | Test/development instance attributes, leave null to skip creation. | object({…}) | | null |
+| [test_instance_defaults](variables-instance.tf#L30) | Test/development instance defaults used for optional configuration. If image is null, COS stable will be used. | object({…}) | | {…} |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| [cloud_config](outputs.tf#L17) | Rendered cloud-config file to be passed as user-data instance metadata. | |
+| [test_instance](outputs-instance.tf#L17) | Optional test instance name and address. | |
diff --git a/modules/cloud-config-container/mysql/README.md b/modules/cloud-config-container/mysql/README.md
index 5e4623697..4c99d5042 100644
--- a/modules/cloud-config-container/mysql/README.md
+++ b/modules/cloud-config-container/mysql/README.md
@@ -86,11 +86,14 @@ module "cos-mysql" {
| [kms_config](variables.tf#L35) | Optional KMS configuration to decrypt passed-in password. Leave null if a plaintext password is used. | object({…}) | | null |
| [mysql_config](variables.tf#L46) | MySQL configuration file content, if null container default will be used. | string | | null |
| [mysql_data_disk](variables.tf#L52) | MySQL data disk name in /dev/disk/by-id/ including the google- prefix. If null the boot disk will be used for data. | string | | null |
+| [test_instance](variables-instance.tf#L17) | Test/development instance attributes, leave null to skip creation. | object({…}) | | null |
+| [test_instance_defaults](variables-instance.tf#L30) | Test/development instance defaults used for optional configuration. If image is null, COS stable will be used. | object({…}) | | {…} |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| [cloud_config](outputs.tf#L17) | Rendered cloud-config file to be passed as user-data instance metadata. | |
+| [test_instance](outputs-instance.tf#L17) | Optional test instance name and address. | |
diff --git a/modules/cloud-config-container/nginx/README.md b/modules/cloud-config-container/nginx/README.md
index 2b2923920..c993eb72e 100644
--- a/modules/cloud-config-container/nginx/README.md
+++ b/modules/cloud-config-container/nginx/README.md
@@ -63,11 +63,14 @@ module "cos-nginx" {
| [files](variables.tf#L53) | Map of extra files to create on the instance, path as key. Owner and permissions will use defaults if null. | map(object({…})) | | {} |
| [image](variables.tf#L29) | Nginx container image. | string | | "nginxdemos/hello:plain-text" |
| [nginx_config](variables.tf#L35) | Nginx configuration path, if null container default will be used. | string | | null |
+| [test_instance](variables-instance.tf#L17) | Test/development instance attributes, leave null to skip creation. | object({…}) | | null |
+| [test_instance_defaults](variables-instance.tf#L30) | Test/development instance defaults used for optional configuration. If image is null, COS stable will be used. | object({…}) | | {…} |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| [cloud_config](outputs.tf#L17) | Rendered cloud-config file to be passed as user-data instance metadata. | |
+| [test_instance](outputs-instance.tf#L17) | Optional test instance name and address. | |
diff --git a/modules/cloud-config-container/onprem/README.md b/modules/cloud-config-container/onprem/README.md
index 29aee5934..222d25b04 100644
--- a/modules/cloud-config-container/onprem/README.md
+++ b/modules/cloud-config-container/onprem/README.md
@@ -68,6 +68,8 @@ module "on-prem" {
| [config_variables](variables.tf#L17) | Additional variables used to render the cloud-config and CoreDNS templates. | map(any) | | {} |
| [coredns_config](variables.tf#L23) | CoreDNS configuration path, if null default will be used. | string | | null |
| [local_ip_cidr_range](variables.tf#L29) | IP CIDR range used for the Docker onprem network. | string | | "192.168.192.0/24" |
+| [test_instance](variables-instance.tf#L17) | Test/development instance attributes, leave null to skip creation. | object({…}) | | null |
+| [test_instance_defaults](variables-instance.tf#L30) | Test/development instance defaults used for optional configuration. If image is null, COS stable will be used. | object({…}) | | {…} |
| [vpn_dynamic_config](variables.tf#L46) | BGP configuration for dynamic VPN, ignored if VPN type is 'static'. | object({…}) | | {…} |
| [vpn_static_ranges](variables.tf#L70) | Remote CIDR ranges for static VPN, ignored if VPN type is 'dynamic'. | list(string) | | ["10.0.0.0/8"] |
@@ -76,5 +78,6 @@ module "on-prem" {
| name | description | sensitive |
|---|---|:---:|
| [cloud_config](outputs.tf#L17) | Rendered cloud-config file to be passed as user-data instance metadata. | |
+| [test_instance](outputs-instance.tf#L17) | Optional test instance name and address. | |
diff --git a/modules/cloud-config-container/squid/README.md b/modules/cloud-config-container/squid/README.md
index e1aa87168..912c52622 100644
--- a/modules/cloud-config-container/squid/README.md
+++ b/modules/cloud-config-container/squid/README.md
@@ -70,11 +70,14 @@ module "cos-squid" {
| [file_defaults](variables.tf#L35) | Default owner and permissions for files. | object({…}) | | {…} |
| [files](variables.tf#L47) | Map of extra files to create on the instance, path as key. Owner and permissions will use defaults if null. | map(object({…})) | | {} |
| [squid_config](variables.tf#L29) | Squid configuration path, if null default will be used. | string | | null |
+| [test_instance](variables-instance.tf#L17) | Test/development instance attributes, leave null to skip creation. | object({…}) | | null |
+| [test_instance_defaults](variables-instance.tf#L30) | Test/development instance defaults used for optional configuration. If image is null, COS stable will be used. | object({…}) | | {…} |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| [cloud_config](outputs.tf#L17) | Rendered cloud-config file to be passed as user-data instance metadata. | |
+| [test_instance](outputs-instance.tf#L17) | Optional test instance name and address. | |
diff --git a/modules/net-vpc-firewall/main.tf b/modules/net-vpc-firewall/main.tf
index ef4767d3a..c08b2d165 100644
--- a/modules/net-vpc-firewall/main.tf
+++ b/modules/net-vpc-firewall/main.tf
@@ -124,14 +124,22 @@ resource "google_compute_firewall" "allow-tag-https" {
resource "google_compute_firewall" "custom-rules" {
# provider = "google-beta"
- for_each = local.custom_rules
- name = each.key
- description = each.value.description
- direction = each.value.direction
- network = var.network
- project = var.project_id
- source_ranges = each.value.direction == "INGRESS" ? each.value.ranges : null
- destination_ranges = each.value.direction == "EGRESS" ? each.value.ranges : null
+ for_each = local.custom_rules
+ name = each.key
+ description = each.value.description
+ direction = each.value.direction
+ network = var.network
+ project = var.project_id
+ source_ranges = (
+ each.value.direction == "INGRESS"
+ ? coalesce(each.value.ranges, []) == [] ? ["0.0.0.0/0"] : each.value.ranges
+ : null
+ )
+ destination_ranges = (
+ each.value.direction == "EGRESS"
+ ? coalesce(each.value.ranges, []) == [] ? ["0.0.0.0/0"] : each.value.ranges
+ : null
+ )
source_tags = each.value.use_service_accounts || each.value.direction == "EGRESS" ? null : each.value.sources
source_service_accounts = each.value.use_service_accounts && each.value.direction == "INGRESS" ? each.value.sources : null
target_tags = each.value.use_service_accounts ? null : each.value.targets
diff --git a/tests/fast/stages/s02_networking_peering/__init__.py b/tests/fast/stages/s02_networking_peering/__init__.py
new file mode 100644
index 000000000..6d6d1266c
--- /dev/null
+++ b/tests/fast/stages/s02_networking_peering/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2022 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.
diff --git a/tests/fast/stages/s02_networking_peering/fixture/main.tf b/tests/fast/stages/s02_networking_peering/fixture/main.tf
new file mode 100644
index 000000000..b06bad39f
--- /dev/null
+++ b/tests/fast/stages/s02_networking_peering/fixture/main.tf
@@ -0,0 +1,44 @@
+/**
+ * Copyright 2022 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 "stage" {
+ source = "../../../../../fast/stages/02-networking-peering"
+ data_dir = "../../../../../fast/stages/02-networking-peering/data/"
+ billing_account = {
+ id = "000000-111111-222222"
+ organization_id = 123456789012
+ }
+ custom_roles = {
+ service_project_network_admin = "organizations/123456789012/roles/foo"
+ }
+ folder_ids = {
+ networking = null
+ networking-dev = null
+ networking-prod = null
+ }
+ service_accounts = {
+ data-platform-dev = "string"
+ data-platform-prod = "string"
+ project-factory-dev = "string"
+ project-factory-prod = "string"
+ }
+ organization = {
+ domain = "fast.example.com"
+ id = 123456789012
+ customer_id = "C00000000"
+ }
+ prefix = "fast2"
+}
diff --git a/tests/fast/stages/s02_networking_peering/test_plan.py b/tests/fast/stages/s02_networking_peering/test_plan.py
new file mode 100644
index 000000000..6189f62e3
--- /dev/null
+++ b/tests/fast/stages/s02_networking_peering/test_plan.py
@@ -0,0 +1,20 @@
+# Copyright 2022 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.
+
+
+def test_counts(fast_e2e_plan_runner):
+ "Test stage."
+ num_modules, num_resources, _ = fast_e2e_plan_runner()
+ # TODO: to re-enable per-module resource count check print _, then test
+ assert num_modules > 0 and num_resources > 0
diff --git a/tests/fast/stages/s02_networking_vpn/fixture/main.tf b/tests/fast/stages/s02_networking_vpn/fixture/main.tf
index 58a8d6c0d..9a736685d 100644
--- a/tests/fast/stages/s02_networking_vpn/fixture/main.tf
+++ b/tests/fast/stages/s02_networking_vpn/fixture/main.tf
@@ -14,22 +14,6 @@
* limitations under the License.
*/
-# module "stage" {
-# source = "../../../../../fast/stages/02-networking-vpn"
-# billing_account_id = "000000-111111-222222"
-# organization = {
-# domain = "gcp-pso-italy.net"
-# id = 856933387836
-# customer_id = "C01lmug8b"
-# }
-# prefix = "fast"
-# project_factory_sa = {
-# dev = "foo@iam"
-# prod = "bar@iam"
-# }
-# data_dir = "../../../../../fast/stages/02-networking-vpn/data/"
-# }
-
module "stage" {
source = "../../../../../fast/stages/02-networking-vpn"
data_dir = "../../../../../fast/stages/02-networking-vpn/data/"
diff --git a/tools/check_documentation.py b/tools/check_documentation.py
index 1902570c1..2a6dd8ecf 100755
--- a/tools/check_documentation.py
+++ b/tools/check_documentation.py
@@ -79,11 +79,15 @@ def main(dirs, exclude_file=None, files=False, show_diffs=False,
for mod_name, state, diff in _check_dir(dir_name, exclude_file, files,
show_extra):
if state == State.FAIL:
- errors.append(diff)
+ errors.append((mod_name, diff))
print(f'[{state_labels[state]}] {mod_name}')
if errors:
if show_diffs:
- print('\n'.join(errors))
+ print('Errored diffs:')
+ print('\n'.join([e[1] for e in errors]))
+ else:
+ print('Errored modules:')
+ print('\n'.join([e[0] for e in errors]))
raise SystemExit('Errors found.')
diff --git a/tools/tfdoc.py b/tools/tfdoc.py
index 0e847d340..68bbe00cc 100755
--- a/tools/tfdoc.py
+++ b/tools/tfdoc.py
@@ -100,10 +100,11 @@ VAR_RE_TYPE = re.compile(r'([\(\{\}\)])')
VAR_TEMPLATE = ('default', 'description', 'type', 'nullable')
File = collections.namedtuple('File', 'name description modules resources')
-Output = collections.namedtuple('Output',
- 'name description sensitive consumers line')
+Output = collections.namedtuple(
+ 'Output', 'name description sensitive consumers file line')
Variable = collections.namedtuple(
- 'Variable', 'name description type default required nullable source line')
+ 'Variable',
+ 'name description type default required nullable source file line')
# parsing functions
@@ -171,41 +172,52 @@ def parse_files(basepath, exclude_files=None):
yield File(shortname, description, modules, resources)
-def parse_outputs(basepath):
- 'Return a list of Output named tuples for root module outputs.tf.'
- try:
- with open(os.path.join(basepath, 'outputs.tf')) as file:
- body = file.read()
- except (IOError, OSError) as e:
- raise SystemExit(f'No outputs file in {basepath}.')
- for item in _parse(body, enum=OUT_ENUM, re=OUT_RE, template=OUT_TEMPLATE):
- description = ''.join(item['description'])
- sensitive = item['sensitive'] != []
- consumers = item['tags'].get('output:consumers', '')
- yield Output(name=item['name'], description=description,
- sensitive=sensitive, consumers=consumers, line=item['line'])
+def parse_outputs(basepath, exclude_files=None):
+ 'Return a list of Output named tuples for root module outputs*.tf.'
+ exclude_files = exclude_files or []
+ for name in glob.glob(os.path.join(basepath, 'outputs*tf')):
+ shortname = os.path.basename(name)
+ if shortname in exclude_files:
+ continue
+ try:
+ with open(name) as file:
+ body = file.read()
+ except (IOError, OSError) as e:
+ raise SystemExit(f'Cannot open outputs file {shortname}.')
+ for item in _parse(body, enum=OUT_ENUM, re=OUT_RE, template=OUT_TEMPLATE):
+ description = ''.join(item['description'])
+ sensitive = item['sensitive'] != []
+ consumers = item['tags'].get('output:consumers', '')
+ yield Output(name=item['name'], description=description,
+ sensitive=sensitive, consumers=consumers, file=shortname,
+ line=item['line'])
-def parse_variables(basepath):
- 'Return a list of Output named tuples for root module variables.tf.'
- try:
- with open(os.path.join(basepath, 'variables.tf')) as file:
- body = file.read()
- except (IOError, OSError) as e:
- raise SystemExit(f'No variables file in {basepath}.')
- for item in _parse(body):
- description = ''.join(item['description'])
- vtype = '\n'.join(item['type'])
- default = HEREDOC_RE.sub(r'\1', '\n'.join(item['default']))
- required = not item['default']
- nullable = item.get('nullable') != ['false']
- source = item['tags'].get('variable:source', '')
- if not required and default != 'null' and vtype == 'string':
- default = f'"{default}"'
+def parse_variables(basepath, exclude_files=None):
+ 'Return a list of Variable named tuples for root module variables*.tf.'
+ exclude_files = exclude_files or []
+ for name in glob.glob(os.path.join(basepath, 'variables*tf')):
+ shortname = os.path.basename(name)
+ if shortname in exclude_files:
+ continue
+ try:
+ with open(name) as file:
+ body = file.read()
+ except (IOError, OSError) as e:
+ raise SystemExit(f'Cannot open variables file {shortname}.')
+ for item in _parse(body):
+ description = ''.join(item['description'])
+ vtype = '\n'.join(item['type'])
+ default = HEREDOC_RE.sub(r'\1', '\n'.join(item['default']))
+ required = not item['default']
+ nullable = item.get('nullable') != ['false']
+ source = item['tags'].get('variable:source', '')
+ if not required and default != 'null' and vtype == 'string':
+ default = f'"{default}"'
- yield Variable(name=item['name'], description=description, type=vtype,
- default=default, required=required, source=source,
- line=item['line'], nullable=nullable)
+ yield Variable(name=item['name'], description=description, type=vtype,
+ default=default, required=required, source=source,
+ file=shortname, line=item['line'], nullable=nullable)
# formatting functions
@@ -268,7 +280,7 @@ def format_outputs(items, show_extra=True):
if consumers:
consumers = '%s' % ' · '.join(consumers.split())
sensitive = '✓' if i.sensitive else ''
- format = f'| [{i.name}](outputs.tf#L{i.line}) | {i.description or ""} | {sensitive} |'
+ format = f'| [{i.name}]({i.file}#L{i.line}) | {i.description or ""} | {sensitive} |'
format += f' {consumers} |' if show_extra else ''
yield format
@@ -301,7 +313,7 @@ def format_variables(items, show_extra=True):
value = f'{value[0]}…{value[-1].strip()}'
vars[k] = f'{_escape(value)}'
format = (
- f'| [{i.name}](variables.tf#L{i.line}) | {i.description or ""} | {vars["type"]} '
+ f'| [{i.name}]({i.file}#L{i.line}) | {i.description or ""} | {vars["type"]} '
f'| {vars["required"]} | {vars["default"]} |')
format += f' {vars["source"]} |' if show_extra else ''
yield format
@@ -342,8 +354,8 @@ def create_doc(module_path, files=False, show_extra=False, exclude_files=None,
show_extra = opts.get('show_extra', show_extra)
try:
mod_files = list(parse_files(module_path, exclude_files)) if files else []
- mod_variables = list(parse_variables(module_path))
- mod_outputs = list(parse_outputs(module_path))
+ mod_variables = list(parse_variables(module_path, exclude_files))
+ mod_outputs = list(parse_outputs(module_path, exclude_files))
except (IOError, OSError) as e:
raise SystemExit(e)
return format_doc(mod_outputs, mod_variables, mod_files, show_extra)