diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml
index 1c63b78dc..a73d8ae20 100644
--- a/.github/workflows/linting.yml
+++ b/.github/workflows/linting.yml
@@ -16,8 +16,6 @@ name: "Linting"
on:
pull_request:
branches:
- - fast-dev
- - fast-dev-gke
- master
tags:
- ci
@@ -71,4 +69,7 @@ jobs:
- name: Check python formatting
id: yapf
run: |
- yapf --style="{based_on_style: google, indent_width: 2, SPLIT_BEFORE_NAMED_ASSIGNS: false}" -p -d tools/*.py
+ yapf --style="{based_on_style: google, indent_width: 2, SPLIT_BEFORE_NAMED_ASSIGNS: false}" -p -d \
+ tools/*.py \
+ blueprints/cloud-operations/network-dashboard/src/*py \
+ blueprints/cloud-operations/network-dashboard/src/plugins/*py
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 3fc3fe56f..3e452275b 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -14,12 +14,10 @@
name: "Tests"
on:
- schedule:
- - cron: "45 2 * * *"
+ # schedule:
+ # - cron: "45 2 * * *"
pull_request:
branches:
- - fast-dev
- - fast-dev-gke
- master
tags:
- ci
diff --git a/.gitignore b/.gitignore
index a2d19a2c5..91778178c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,13 +21,13 @@ bundle.zip
**/*.pkrvars.hcl
fixture_*
fast/configs
-fast/stages/**/[0-9]*providers.tf
-fast/stages/**/terraform.tfvars
-fast/stages/**/terraform.tfvars.json
-fast/stages/**/terraform-*.auto.tfvars.json
-fast/stages/**/0*.auto.tfvars*
+fast/**/[0-9]*providers.tf
+fast/**/terraform.tfvars
+fast/**/terraform.tfvars.json
+fast/**/terraform-*.auto.tfvars.json
+fast/**/[0-9]*.auto.tfvars*
**/node_modules
-fast/stages/**/globals.auto.tfvars.json
+fast/**/globals.auto.tfvars.json
cloud_sql_proxy
examples/cloud-operations/binauthz/tenant-setup.yaml
examples/cloud-operations/binauthz/app/app.yaml
@@ -37,8 +37,15 @@ examples/cloud-operations/adfs/ansible/gssh.sh
examples/cloud-operations/multi-cluster-mesh-gke-fleet-api/ansible/vars.yaml
examples/cloud-operations/multi-cluster-mesh-gke-fleet-api/ansible/gssh.sh
blueprints/cloud-operations/network-dashboard/cloud-function.zip
-blueprints/cloud-operations/apigee/bundle-export.zip
-blueprints/cloud-operations/apigee/bundle-gcs2bq.zip
-blueprints/cloud-operations/apigee/apiproxy.zip
-blueprints/cloud-operations/apigee/create-datastore.sh
-blueprints/cloud-operations/apigee/deploy-apiproxy.sh
+blueprints/apigee/bigquery-analytics/bundle-export.zip
+blueprints/apigee/bigquery-analytics/bundle-gcs2bq.zip
+blueprints/apigee/bigquery-analytics/apiproxy.zip
+blueprints/apigee/bigquery-analytics/create-datastore.sh
+blueprints/apigee/bigquery-analytics/deploy-apiproxy.sh
+blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/bundle/apiproxy/targets/default.xml
+blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/bundle.zip
+blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/deploy-apiproxy.sh
+blueprints/apigee/hybrid-gke/apiproxy.zip
+blueprints/apigee/hybrid-gke/deploy-apiproxy.sh
+blueprints/apigee/hybrid-gke/ansible/gssh.sh
+blueprints/apigee/hybrid-gke/ansible/vars/vars.yaml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 75497c848..4d6ab8295 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,10 +4,116 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
-
+
+
+### DOCUMENTATION
+
+- [[#1052](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1052)] **incompatible change:** FAST multitenant bootstrap and resource management, rename org-level FAST stages ([ludoo](https://github.com/ludoo))
+
+### FAST
+
+- [[#1052](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1052)] **incompatible change:** FAST multitenant bootstrap and resource management, rename org-level FAST stages ([ludoo](https://github.com/ludoo))
+
+### MODULES
+
+- [[#1052](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1052)] **incompatible change:** FAST multitenant bootstrap and resource management, rename org-level FAST stages ([ludoo](https://github.com/ludoo))
+
+### TOOLS
+
+- [[#1052](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1052)] **incompatible change:** FAST multitenant bootstrap and resource management, rename org-level FAST stages ([ludoo](https://github.com/ludoo))
+
+## [20.0.0] - 2023-02-04
+
### BLUEPRINTS
+- [[#1038](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1038)] Vertex Pipelines MLOps framework blueprint ([javiergp](https://github.com/javiergp))
+- [[#1124](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1124)] Removed unused file package-lock.json ([apichick](https://github.com/apichick))
+- [[#1119](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1119)] **incompatible change:** Multi-Cluster Ingress gateway api config ([wiktorn](https://github.com/wiktorn))
+- [[#1111](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1111)] **incompatible change:** In the apigee module now both the /22 and /28 peering IP ranges are p… ([apichick](https://github.com/apichick))
+- [[#1106](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1106)] Network Dashboard: PSA support for Filestore and Memorystore ([aurelienlegrand](https://github.com/aurelienlegrand))
+- [[#1110](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1110)] Bump cookiejar from 2.1.3 to 2.1.4 in /blueprints/apigee/bigquery-analytics/functions/export ([dependabot[bot]](https://github.com/dependabot[bot]))
+- [[#1097](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1097)] Use terraform resource to activate Anthos Service Mesh ([wiktorn](https://github.com/wiktorn))
+- [[#1104](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1104)] Updated apigee hybrid for gke README ([apichick](https://github.com/apichick))
+- [[#1107](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1107)] Check linting for Python dashboard files ([ludoo](https://github.com/ludoo))
+- [[#1102](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1102)] Improvements in apigee hybrid-gke: now using workload identity and GLB ([apichick](https://github.com/apichick))
+- [[#1098](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1098)] Add shared-vpc support on data-playground blueprint ([lcaggio](https://github.com/lcaggio))
+- [[#1095](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1095)] [Data Platform] Fix Table in readme ([lcaggio](https://github.com/lcaggio))
+- [[#1089](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1089)] Update Data Platform ([lcaggio](https://github.com/lcaggio))
+- [[#1081](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1081)] Apigee hybrid on GKE ([apichick](https://github.com/apichick))
+- [[#1082](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1082)] Fixes in Apigee Bigquery Analytics blueprint ([apichick](https://github.com/apichick))
+- [[#1071](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1071)] Moved apigee bigquery analytics blueprint, added apigee network patterns ([apichick](https://github.com/apichick))
+- [[#1073](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1073)] Allow setting no ranges in firewall module custom rules ([ludoo](https://github.com/ludoo))
+- [[#1072](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1072)] **incompatible change:** Add gc_policy to Bigtable module, bump provider versions to 4.47 ([iht](https://github.com/iht))
+- [[#1063](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1063)] Network dashboard: PSA ranges support, starting with Cloud SQL ([aurelienlegrand](https://github.com/aurelienlegrand))
+- [[#1062](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1062)] Fixes for GKE ([wiktorn](https://github.com/wiktorn))
+- [[#1060](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1060)] Update src/README.md for Network Dashboard ([aurelienlegrand](https://github.com/aurelienlegrand))
+- [[#1020](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1020)] Networking dashboard and discovery tool refactor ([ludoo](https://github.com/ludoo))
+
+### DOCUMENTATION
+
+- [[#1101](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1101)] First batch of testing updates to core modules ([juliocc](https://github.com/juliocc))
+- [[#1089](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1089)] Update Data Platform ([lcaggio](https://github.com/lcaggio))
+- [[#1084](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1084)] Fixes in Apigee blueprints README files ([apichick](https://github.com/apichick))
+- [[#1081](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1081)] Apigee hybrid on GKE ([apichick](https://github.com/apichick))
+- [[#1074](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1074)] Adding new section for Authentication issues ([agutta](https://github.com/agutta))
+- [[#1071](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1071)] Moved apigee bigquery analytics blueprint, added apigee network patterns ([apichick](https://github.com/apichick))
+- [[#1057](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1057)] Adding new file FAQ and an image ([agutta](https://github.com/agutta))
+
+### FAST
+
+- [[#1118](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1118)] Add missing logging admin role for initial user ([ludoo](https://github.com/ludoo))
+- [[#1099](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1099)] Fix destroy in stage 1 outputs ([ludoo](https://github.com/ludoo))
+- [[#1089](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1089)] Update Data Platform ([lcaggio](https://github.com/lcaggio))
+- [[#1085](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1085)] fix restricted services not being added to the perimeter configurations ([drebes](https://github.com/drebes))
+- [[#1057](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1057)] Adding new file FAQ and an image ([agutta](https://github.com/agutta))
+- [[#1054](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1054)] FAST: fix typo in bootstrap stage README ([agutta](https://github.com/agutta))
+- [[#1051](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1051)] FAST: add instructions for billing export to stage 0 README ([KPRepos](https://github.com/KPRepos))
+
+### MODULES
+
+- [[#1127](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1127)] Skip node config for autopilot ([ludoo](https://github.com/ludoo))
+- [[#1125](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1125)] Added mesh_certificates setting in GKE cluster ([rosmo](https://github.com/rosmo))
+- [[#1094](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1094)] Added GLB example with MIG as backend ([eliamaldini](https://github.com/eliamaldini))
+- [[#1119](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1119)] **incompatible change:** Multi-Cluster Ingress gateway api config ([wiktorn](https://github.com/wiktorn))
+- [[#1111](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1111)] **incompatible change:** In the apigee module now both the /22 and /28 peering IP ranges are p… ([apichick](https://github.com/apichick))
+- [[#1116](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1116)] Include cloudbuild API in project module ([aymanfarhat](https://github.com/aymanfarhat))
+- [[#1115](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1115)] add new parameters support in apigee module ([blackillzone](https://github.com/blackillzone))
+- [[#1112](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1112)] Add HTTPS frontend with SNEG example ([juliodiez](https://github.com/juliodiez))
+- [[#1097](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1097)] Use terraform resource to activate Anthos Service Mesh ([wiktorn](https://github.com/wiktorn))
+- [[#1101](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1101)] First batch of testing updates to core modules ([juliocc](https://github.com/juliocc))
+- [[#1098](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1098)] Add shared-vpc support on data-playground blueprint ([lcaggio](https://github.com/lcaggio))
+- [[#1096](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1096)] [VPC-SC] Add support for scoped Policies ([lcaggio](https://github.com/lcaggio))
+- [[#1093](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1093)] Added tags to gke-cluster module ([apichick](https://github.com/apichick))
+- [[#1078](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1078)] Fixed delete_rule in compute-mig module for stateful disks ([rosmo](https://github.com/rosmo))
+- [[#1080](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1080)] Added device_name field to compute-vm attached_disks parameter ([rosmo](https://github.com/rosmo))
+- [[#1079](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1079)] Reorder org policy rules ([juliocc](https://github.com/juliocc))
+- [[#1075](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1075)] **incompatible change:** Add cluster replicas to Bigtable module. ([iht](https://github.com/iht))
+- [[#1073](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1073)] Allow setting no ranges in firewall module custom rules ([ludoo](https://github.com/ludoo))
+- [[#1072](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1072)] **incompatible change:** Add gc_policy to Bigtable module, bump provider versions to 4.47 ([iht](https://github.com/iht))
+- [[#1070](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1070)] Fix MIG health check variable ([ludoo](https://github.com/ludoo))
+- [[#1069](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1069)] Allow tables with several column families in Bigtable ([iht](https://github.com/iht))
+- [[#1068](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1068)] Added endpoint_attachment_hosts output to apigee module ([apichick](https://github.com/apichick))
+- [[#1067](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1067)] Corrected load balancing scheme in backend service ([apichick](https://github.com/apichick))
+- [[#1066](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1066)] Refactor GCS module and tests for Terraform 1.3 ([ludoo](https://github.com/ludoo))
+- [[#1062](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1062)] Fixes for GKE ([wiktorn](https://github.com/wiktorn))
+- [[#1061](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1061)] **incompatible change:** Allow using dynamically generated address in LB modules NEGs ([ludoo](https://github.com/ludoo))
+- [[#1059](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1059)] Read ranges from correct fields in firewall factory ([juliocc](https://github.com/juliocc))
+- [[#1056](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1056)] Feature - CloudSQL pre-allocation private IP range and GKE Cluster ignore_change lifecycle hook. ([itsavvy-ankur](https://github.com/itsavvy-ankur))
+
+### TOOLS
+
+- [[#1107](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1107)] Check linting for Python dashboard files ([ludoo](https://github.com/ludoo))
+- [[#1101](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1101)] First batch of testing updates to core modules ([juliocc](https://github.com/juliocc))
+- [[#1091](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1091)] Fix check_documentation output ([juliocc](https://github.com/juliocc))
+- [[#1053](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1053)] Extend inventory-based testing to examples ([juliocc](https://github.com/juliocc))
+
+## [19.0.0] - 2022-12-13
+
+
+### BLUEPRINTS
+
+- [[#1045](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1045)] Assorted module fixes ([ludoo](https://github.com/ludoo))
- [[#1044](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1044)] **incompatible change:** Refactor net-glb module for Terraform 1.3 ([ludoo](https://github.com/ludoo))
- [[#982](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/982)] Adding Secondary IP Utilization calculation ([brianhmj](https://github.com/brianhmj))
- [[#1037](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1037)] Bump qs and formidable in /blueprints/cloud-operations/apigee/functions/export ([dependabot[bot]](https://github.com/dependabot[bot]))
@@ -69,6 +175,8 @@ All notable changes to this project will be documented in this file.
### DOCUMENTATION
+- [[#1048](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1048)] Document new testing approach ([ludoo](https://github.com/ludoo))
+- [[#1045](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1045)] Assorted module fixes ([ludoo](https://github.com/ludoo))
- [[#1014](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1014)] Update typos in `net-vpc-firewall` README.md ([aymanfarhat](https://github.com/aymanfarhat))
- [[#1044](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1044)] **incompatible change:** Refactor net-glb module for Terraform 1.3 ([ludoo](https://github.com/ludoo))
- [[#1009](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1009)] Fix encryption in Data Playground blueprint ([lcaggio](https://github.com/lcaggio))
@@ -123,6 +231,8 @@ All notable changes to this project will be documented in this file.
### MODULES
+- [[#1049](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1049)] Add ssl certs to cloudsql instance ([prabhaarya](https://github.com/prabhaarya))
+- [[#1045](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1045)] Assorted module fixes ([ludoo](https://github.com/ludoo))
- [[#1040](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1040)] Fix name in google_pubsub_schema resource ([VictorCavalcanteLG](https://github.com/VictorCavalcanteLG))
- [[#1043](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1043)] added reverse lookup feature to module dns #1042 ([chemapolo](https://github.com/chemapolo))
- [[#1044](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1044)] **incompatible change:** Refactor net-glb module for Terraform 1.3 ([ludoo](https://github.com/ludoo))
@@ -211,6 +321,7 @@ All notable changes to this project will be documented in this file.
### TOOLS
+- [[#1048](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1048)] Document new testing approach ([ludoo](https://github.com/ludoo))
- [[#1029](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1029)] Testing framework revamp ([juliocc](https://github.com/juliocc))
- [[#1022](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1022)] Replace `set-output` with env variable and remove single quotes on labels ([kunzese](https://github.com/kunzese))
- [[#1021](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1021)] Add OpenContainers annotations to published container images ([kunzese](https://github.com/kunzese))
@@ -387,7 +498,7 @@ All notable changes to this project will be documented in this file.
- fix `tag` output on `data-catalog-policy-tag` module
- add shared-vpc support on `gcs-to-bq-with-least-privileges`
- new `net-ilb-l7` module
-- new [02-networking-peering](fast/stages/02-networking-peering) networking stage
+- new `02-networking-peering` networking stage
- **incompatible change** the variable for PSA ranges in networking stages have changed
## [14.0.0] - 2022-02-25
@@ -406,8 +517,8 @@ All notable changes to this project will be documented in this file.
- **incompatible change** removed `ingress_settings` configuration option in the `cloud-functions` module.
- new [m4ce VM example](blueprints/cloud-operations/vm-migration/)
- Support for resource management tags in the `organization`, `folder`, `project`, `compute-vm`, and `kms` modules
-- new [data platform](fast/stages/03-data-platform) stage 3
-- new [02-networking-nva](fast/stages/02-networking-nva) networking stage
+- new `data platform` stage 3
+- new `02-networking-nva` networking stage
- allow customizing the names of custom roles
- added `environment` and `context` resource management tags
- use resource management tags to restrict scope of roles/orgpolicy.policyAdmin
@@ -842,7 +953,9 @@ All notable changes to this project will be documented in this file.
- merge development branch with suite of new modules and end-to-end examples
-[Unreleased]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v18.0.0...HEAD
+[Unreleased]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v20.0.0...HEAD
+[20.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v19.0.0...v20.0.0
+[19.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v18.0.0...v19.0.0
[18.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v16.0.0...v18.0.0
[16.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v15.0.0...v16.0.0
[15.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v14.0.0...v15.0.0
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d82b1ac77..d34ad2d99 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -72,11 +72,13 @@ pytest tests/examples
Once everything looks good, add/commit any pending changes then push and open a PR on GitHub. We typically enforce a set of design and style conventions, so please make sure you have familiarized yourself with the following sections and implemented them in your code, to avoid lengthy review cycles.
HINT: if you work on high-latency or low-bandwidth network use `TF_PLUGIN_CACHE_DIR` environment variable to dramatically speed up the tests, for example:
+
```bash
TF_PLUGIN_CACHE_DIR=/tmp/tfcache pytest tests
```
Or just add into your [terraformrc](https://developer.hashicorp.com/terraform/cli/config/config-file):
+
```
plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"
```
@@ -544,6 +546,7 @@ locals {
#### The `prefix` variable
If you would like to use a "prefix" variable for resource names, please keep its definition consistent across all modules:
+
```hcl
# variables.tf
variable "prefix" {
@@ -563,6 +566,7 @@ locals {
```
For blueprints the prefix is mandatory:
+
```hcl
variable "prefix" {
description = "Prefix used for resource names."
@@ -608,6 +612,7 @@ The linting workflow tests:
- that the correct copyright boilerplate is present in all files, using `tools/check_boilerplate.py`
- that all Terraform code is linted via `terraform fmt`
+- that Terraform variables and outputs are sorted alphabetically
- that all README files have up to date outputs, variables, and files (where relevant) tables, via `tools/check_documentation.py`
- that all links in README files are syntactically correct and valid if internal, via `tools/check_links.py`
- that resource names used in FAST stages stay within a length limit, via `tools/check_names.py`
@@ -639,90 +644,123 @@ The test workflow runs test suites in parallel. Refer to the next section for mo
#### Using and writing tests
-Our testing approach follows a simple philosophy: we mainly test to ensure code works, and it does not break due to changes to dependencies (modules) or provider resources.
+Our testing approach follows a simple philosophy: we mainly test to ensure code works, and that it does not break due to changes to dependencies (modules) or provider resources.
This makes testing very simple, as a successful `terraform plan` run in a test case is often enough. We only write more specialized tests when we need to check the output of complex transformations in `for` loops.
-As our testing needs are very simple, we also wanted to reduce the friction required to write new tests as much as possible: our tests are written in Python, and use `pytest` which is the standard for the language. We adopted this approach instead of others (Inspec/Kitchen, Terratest) as it allows writing simple functions as test units using Python which is simple and widely known.
+As our testing needs are very simple, we also wanted to reduce the friction required to write new tests as much as possible: our tests are written in Python and use `pytest` which is the standard for the language, leveraging our [`tftest`](https://pypi.org/project/tftest/) library, which wraps the Terraform executable and returns familiar data structures for most commands.
-The last piece of our testing framework is our [`tftest`](https://pypi.org/project/tftest/) library, which wraps the Terraform executable and returns familiar data structures for most commands.
+Writing `pytest` unit tests to check plan results is really easy, but since wrapping modules and examples in dedicated fixtures and hand-coding checks gets annoying after a while, we developed a thin layer that allows us to use `tfvars` files to run tests, and `yaml` results to check results. In some specific situations you might still want to interact directly with `tftest` via Python, if that's the case skip to the legacy approach below.
-##### Testing end-to-end examples
+##### Testing end-to-end examples via `tfvars` and `yaml`
-Putting it all together, here is how an end-to-end blueprint test works.
+Our new approach to testing requires you to:
-Each example is a Python module in its own directory, and a Terraform fixture that calls the example as a module:
+- create a folder in the right `tests` hierarchy where specific test files will be hosted
+- define `tfvars` files each with a specific variable configuration to test
+- define `yaml` "inventory" files with the plan and output results you want to test
+- declare which of these files need to be run as tests in a `tftest.yaml` file
+
+Let's go through each step in succession, assuming you are testing the new `net-glb` module.
+
+First create a new folder under `tests/modules` replacing any dash in the module name with underscores. You also need to create an empty `__init__.py` file in it, since the folder represents a package from the point of view of `pytest`. Note that if you were testing a blueprint the folder would go in `tests/blueprints`.
```bash
-tests/blueprints/cloud_operations/iam_delegated_role_grants/
-├── fixture
-│ ├── main.tf
-│ └── variables.tf
-├── __init__.py
-└── test_plan.py
+mkdir tests/modules/net_glb
+touch tests/modules/net_glb/__init__.py
```
-One point of note is that the folder contains a Python module, so any dash needs to be replaced with underscores to make it importable. The actual test in the `test_plan.py` file looks like this:
-
-```python
-def test_resources(e2e_plan_runner):
- "Test that plan works and the numbers of resources is as expected."
- modules, resources = e2e_plan_runner()
- assert len(modules) == 6
- assert len(resources) == 18
-```
-
-It uses our pytest `e2e_plan_runner` fixture, which assumes a Terraform test setup is present in the `fixture` folder alongside the test file, runs `plan` on it, and returns the number of modules and resources.
-
-The Terraform fixture is a single block that runs the whole example as a module, and a handful of variables that can be used to test different configurations (not used above so they could be replaced with static strings).
+Then define a `tfvars` file with one of the module configurations you want to test. If you have a lot of variables which are shared across different tests, you can group all the common variables in a single `tfvars` file and associate it with each test's specific `tfvars` file (check the [organization module test](./tests/modules/organization/tftest.yaml) for an example).
```hcl
-module "test" {
- source = "../../../../../blueprints/cloud-operations/asset-inventory-feed-remediation"
- project_create = var.project_create
- project_id = var.project_id
+# file: tests/modules/net_glb/test-simple.tfvars
+name = "glb-test-0"
+project_id = "my-project"
+backend_buckets_config = {
+ default = {
+ bucket_name = "my-bucket"
+ }
}
```
-You can run this test as part of or entire suite of tests, the blueprints suite, or individually:
+Next define the corresponding inventory `yaml` file which will be used to assert values from the plan that uses the `tfvars` file above. In the inventory file you have three sections available:
+
+- `values` is a map of resource indexes (the same ones used by Terraform state) and their attribute name and values; you can define just the attributes you are interested in and the other will be ignored
+- `counts` is a map of resource types (eg `google_compute_engine`) and the number of times each type occurs in the plan; here too just define the ones the need checking
+- `outputs` is a map of outputs and their values; where a value is unknown at plan time use the special `__missing__` token
+
+```yaml
+# file: tests/modules/net_glb/test-simple.yaml
+values:
+ google_compute_global_forwarding_rule.default:
+ description: Terraform managed.
+ load_balancing_scheme: EXTERNAL
+ google_compute_target_http_proxy.default[0]:
+ name: glb-test-1
+counts:
+ google_compute_backend_bucket: 1
+ google_compute_global_forwarding_rule: 1
+ google_compute_health_check: 1
+ google_compute_target_http_proxy: 1
+ google_compute_url_map: 1
+outputs:
+ address: __missing__
+ backend_service_ids: __missing__
+ forwarding_rule: __missing__
+ group_ids: __missing__
+ health_check_ids: __missing__
+ neg_ids: __missing__
+```
+
+Create as many pairs of `tfvars`/`yaml` files as you need to test every scenario and feature, then create the file that triggers our fixture and converts them into `pytest` tests.
+
+```yaml
+# file: tests/modules/net_glb/tftest.yaml
+module: modules/net-glb
+# if there are variables shared among all tests you can define a common file
+# common_tfvars:
+# - defaults.tfvars
+tests:
+ test-plan:
+ tfvars:
+ - test-plan.tfvars
+ - test-plan-extra.tfvars
+```
+
+A good example of tests showing different ways of leveraging our framework is in the [`tests/modules/organization`](./tests/modules/organization) folder.
+
+##### Writing tests in Python (legacy approach)
+
+Where possible, we recommend using the testing framework described in the previous section. However, if you need it, you can still write tests using Python directly.
+
+In general, you should try to use the `plan_summary` fixture, which runs a a terraform plan and returns a `PlanSummary` object. The most important arguments to `plan_summary` are:
+- the path of the Terraform module you want to test, relative to the root of the repository
+- a list of paths representing the tfvars file to pass in to terraform. These paths are relative to the python file defining the test.
+
+If successful, `plan_summary` will return a `PlanSummary` object with the `values`, `counts` and `outputs` attributes following the same semantics described in the previous section. You can use this fields to write your custom tests.
+
+Like before let's imagine we're writing a (python) test for `net-glb` module. First create a new folder under `tests/modules` replacing any dash in the module name with underscores. You also need to create an empty `__init__.py` file in it, to ensure `pytest` discovers you new tests automatically.
```bash
-# run all tests
-pytest
-# only run example tests
-pytest tests/blueprints
-# only run this example tests
-pytest tests/blueprints/cloud_operations/iam_delegated_role_grants/
-# only run a single unit
-pytest tests/blueprints/cloud_operations/iam_delegated_role_grants/test_plan.py::test_resources
+mkdir tests/modules/net_glb
+touch tests/modules/net_glb/__init__.py
```
-##### Testing modules
-
-The same approach used above can also be used for testing modules when a simple plan is enough to validate code. When specific features need to be tested though, the `plan_runner` pytest fixture can be used so that plan resources are returned for inspection.
-
-The following example from the `project` module leverages variables in the Terraform fixture to define which module resources are returned from plan.
-
+Now create a file containing your tests, e.g. `test_plan.py`:
```python
-def test_iam(plan_runner):
- "Test IAM bindings."
- iam = (
- '{"roles/owner" = ["user:one@example.org"],'
- '"roles/viewer" = ["user:two@example.org", "user:three@example.org"]}'
- )
- _, resources = plan_runner(iam=iam)
- roles = dict((r['values']['role'], r['values']['members'])
- for r in resources if r['type'] == 'google_project_iam_binding')
- assert roles == {
- 'roles/owner': ['user:one@example.org'],
- 'roles/viewer': ['user:three@example.org', 'user:two@example.org']}
+def test_name(plan_summary, tfvars_to_yaml, tmp_path):
+ s = plan_summary('modules/net-glb', tf_var_files=['test-plan.tfvars'])
+ address = 'google_compute_url_map.default'
+ assert s.values[address]['project'] == 'my-project'
```
+For more examples on how to write python tests, check the tests for the [`organization`](./tests/modules/organization/test_plan_org_policies.py) module.
+
#### Testing documentation examples
-Most of our documentation examples are also tested via the `examples` test suite. To enable an example for testing just use the special `tftest` comment as the last line in the example, listing the number of modules and resources tested.
+Most of our documentation examples are also tested via the `examples` test suite. To enable an example for testing just use the special `tftest` comment as the last line in the example, listing the number of modules and resources expected.
-A few preset variables are available for use, as shown in this example from the `dns` module documentation.
+A [few preset variables](./tests/examples/variables.tf) are available for use, as shown in this example from the `dns` module documentation.
```hcl
module "private-dns" {
@@ -739,6 +777,25 @@ module "private-dns" {
# tftest modules=1 resources=2
```
+Note that all HCL code examples in READMEs are automatically tested. To prevent this behavior, include `tftest skip` somewhere in the code.
+
+#### Running tests from a temporary directory
+
+ Most of the time you can run tests using the `pytest` command as described in previous. However, the `plan_summary` fixture allows copying the root module and running the test from a temporary directory.
+
+To enable this option, just define the environment variable `TFTEST_COPY` and any tests using the `plan_summary` fixture will automatically run from a temporary directory.
+
+Running tests from temporary directories is useful if:
+- you're running tests in parallel using `pytest-xdist`. In this case, just run you tests as follows:
+ ```bash
+ TFTEST_COPY=1 pytest -n 4
+ ```
+- you're running tests for the `fast/` directory which contain tfvars and auto.tfvars files (which are read by terraform automatically) making your tests fail. In this case, you can run
+ ```
+ TFTEST_COPY=1 pytest fast/
+ ```
+
+
#### Fabric tools
The main tool you will interact with in development is `tfdoc`, used to generate file, output and variable tables in README documents.
diff --git a/blueprints/README.md b/blueprints/README.md
index 49bf16ab7..60a84912d 100644
--- a/blueprints/README.md
+++ b/blueprints/README.md
@@ -4,11 +4,12 @@ This section provides **[networking blueprints](./networking/)** that implement
Currently available blueprints:
+- **apigee** - [Apigee Hybrid on GKE](./apigee/hybrid-gke/), [Apigee X analytics in BigQuery](./apigee/bigquery-analytics), [Apigee network patterns](./apigee/network-patterns/)
- **cloud operations** - [Active Directory Federation Services](./cloud-operations/adfs), [Cloud Asset Inventory feeds for resource change tracking and remediation](./cloud-operations/asset-inventory-feed-remediation), [Fine-grained Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Cloud DNS & Shared VPC design](./cloud-operations/dns-shared-vpc), [Delegated Role Grants](./cloud-operations/iam-delegated-role-grants), [Networking Dashboard](./cloud-operations/network-dashboard), [Managing on-prem service account keys by uploading public keys](./cloud-operations/onprem-sa-key-management), [Compute Image builder with Hashicorp Packer](./cloud-operations/packer-image-builder), [Packer example](./cloud-operations/packer-image-builder/packer), [Compute Engine quota monitoring](./cloud-operations/quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq), [Configuring workload identity federation for Terraform Cloud/Enterprise workflow](./cloud-operations/terraform-enterprise-wif), [TCP healthcheck and restart for unmanaged GCE instances](./cloud-operations/unmanaged-instances-healthcheck), [Migrate for Compute Engine (v5) blueprints](./cloud-operations/vm-migration), [Configuring workload identity federation to access Google Cloud resources from apps running on Azure](./cloud-operations/workload-identity-federation)
-- **data solutions** - [GCE and GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion), [Data Platform](./data-solutions/data-platform-foundations), [Spinning up a foundation data pipeline on Google Cloud using Cloud Storage, Dataflow and BigQuery](./data-solutions/gcs-to-bq-with-least-privileges), [#SQL Server Always On Groups blueprint](./data-solutions/sqlserver-alwayson), [Data Playground](./data-solutions/data-playground)
+- **data solutions** - [GCE and GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion), [Data Platform](./data-solutions/data-platform-foundations), [Spinning up a foundation data pipeline on Google Cloud using Cloud Storage, Dataflow and BigQuery](./data-solutions/gcs-to-bq-with-least-privileges), [#SQL Server Always On Groups blueprint](./data-solutions/sqlserver-alwayson), [Data Playground](./data-solutions/data-playground), [MLOps with Vertex AI](./data-solutions/vertex-mlops), [Shielded Folder](./data-solutions/shielded-folder)
- **factories** - [The why and the how of Resource Factories](./factories), [Google Cloud Identity Group Factory](./factories/cloud-identity-group-factory), [Google Cloud BQ Factory](./factories/bigquery-factory), [Google Cloud VPC Firewall Factory](./factories/net-vpc-firewall-yaml), [Minimal Project Factory](./factories/project-factory)
- **GKE** - [Binary Authorization Pipeline Blueprint](./gke/binauthz), [Storage API](./gke/binauthz/image), [Multi-cluster mesh on GKE (fleet API)](./gke/multi-cluster-mesh-gke-fleet-api), [GKE Multitenant Blueprint](./gke/multitenant-fleet), [Shared VPC with GKE support](./networking/shared-vpc-gke/)
-- **networking** - [Decentralized firewall management](./networking/decentralized-firewall), [Decentralized firewall validator](./networking/decentralized-firewall/validator), [Network filtering with Squid](./networking/filtering-proxy), [Network filtering with Squid with isolated VPCs using Private Service Connect](./networking/filtering-proxy-psc), [HTTP Load Balancer with Cloud Armor](./networking/glb-and-armor), [Hub and Spoke via VPN](./networking/hub-and-spoke-vpn), [Hub and Spoke via VPC Peering](./networking/hub-and-spoke-peering), [Internal Load Balancer as Next Hop](./networking/ilb-next-hop), [On-prem DNS and Google Private Access](./networking/onprem-google-access-dns), [Calling a private Cloud Function from On-premises](./networking/private-cloud-function-from-onprem), [Hybrid connectivity to on-premise services through PSC](./networking/psc-hybrid), [PSC Producer](./networking/psc-hybrid/psc-producer), [PSC Consumer](./networking/psc-hybrid/psc-consumer), [Shared VPC with optional GKE cluster](./networking/shared-vpc-gke)
+- **networking** - [Decentralized firewall management](./networking/decentralized-firewall), [Decentralized firewall validator](./networking/decentralized-firewall/validator), [Network filtering with Squid](./networking/filtering-proxy), [Network filtering with Squid with isolated VPCs using Private Service Connect](./networking/filtering-proxy-psc), [HTTP Load Balancer with Cloud Armor](./networking/glb-and-armor), [Hub and Spoke via VPN](./networking/hub-and-spoke-vpn), [Hub and Spoke via VPC Peering](./networking/hub-and-spoke-peering), [Internal Load Balancer as Next Hop](./networking/ilb-next-hop), On-prem DNS and Google Private Access, [Calling a private Cloud Function from On-premises](./networking/private-cloud-function-from-onprem), [Hybrid connectivity to on-premise services through PSC](./networking/psc-hybrid), [PSC Producer](./networking/psc-hybrid/psc-producer), [PSC Consumer](./networking/psc-hybrid/psc-consumer), [Shared VPC with optional GKE cluster](./networking/shared-vpc-gke)
- **serverless** - [Creating multi-region deployments for API Gateway](./serverless/api-gateway)
- **third party solutions** - [OpenShift on GCP user-provisioned infrastructure](./third-party-solutions/openshift), [Wordpress deployment on Cloud Run](./third-party-solutions/wordpress/cloudrun)
diff --git a/blueprints/apigee/README.md b/blueprints/apigee/README.md
new file mode 100644
index 000000000..4cec9de9c
--- /dev/null
+++ b/blueprints/apigee/README.md
@@ -0,0 +1,24 @@
+# Apigee Blueprints
+
+The blueprints in this folder contain a variety of deployment scenarios for Apigee Hybrid and Apigee X.
+
+## Blueprints
+
+### Apigee Hybrid on GKE
+
+
This [blueprint](./hybrid-gke/) shows how to do a non-prod deployment of Apigee Hybrid on GKE(../factories/net-vpc-firewall-yaml/).
+
+
+
+### Apigee X analytics in BigQuery
+
+
This [blueprint](./bigquery-analytics/) shows how to export on a daily basis the Apigee analytics of an organization to a BigQuery table.
+
+
+
+### Apigee X network patterns
+
+The following blueprints demonstrate a set of networking scenarios that can be implemented for Apigee X deployments.
+
+#### Apigee X - Northbound: GLB with PSC Neg, Southbouth: PSC with ILB (L7) and Hybrid NEG
+
This [blueprint](./network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/) shows how to expose an on-prem target backend to clients in the Internet.
\ No newline at end of file
diff --git a/blueprints/cloud-operations/apigee/README.md b/blueprints/apigee/bigquery-analytics/README.md
similarity index 83%
rename from blueprints/cloud-operations/apigee/README.md
rename to blueprints/apigee/bigquery-analytics/README.md
index 0802c433b..027f28ead 100644
--- a/blueprints/cloud-operations/apigee/README.md
+++ b/blueprints/apigee/bigquery-analytics/README.md
@@ -19,7 +19,7 @@ Note: This setup only works if you are not using custom analytics.
## Running the blueprint
-1. Clone this repository or [open it in cloud shell](https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2Fterraform-google-modules%2Fcloud-foundation-fabric&cloudshell_print=cloud-shell-readme.txt&cloudshell_working_dir=blueprints%2Fcloud-operations%apigee), then go through the following steps to create resources:
+1. Clone this repository or [open it in cloud shell](https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2Fterraform-google-modules%2Fcloud-foundation-fabric&cloudshell_print=cloud-shell-readme.txt&cloudshell_working_dir=blueprints%2Fapigee%2Fbigquery-analytics), then go through the following steps to create resources:
2. Copy the file [terraform.tfvars.sample](./terraform.tfvars.sample) to a file called ```terraform.tfvars``` and update the values if required.
@@ -60,14 +60,14 @@ Do the following to verify that everything works as expected.
|---|---|:---:|:---:|:---:|
| [envgroups](variables.tf#L24) | Environment groups (NAME => [HOSTNAMES]). | map(list(string)) | ✓ | |
| [environments](variables.tf#L30) | Environments. | map(object({…})) | ✓ | |
-| [instances](variables.tf#L45) | Instance. | map(object({…})) | ✓ | |
-| [project_id](variables.tf#L91) | Project ID. | string | ✓ | |
-| [psc_config](variables.tf#L97) | PSC configuration. | map(string) | ✓ | |
+| [instances](variables.tf#L45) | Instance. | map(object({…})) | ✓ | |
+| [project_id](variables.tf#L92) | Project ID. | string | ✓ | |
+| [psc_config](variables.tf#L98) | PSC configuration. | map(string) | ✓ | |
| [datastore_name](variables.tf#L17) | Datastore. | string | | "gcs" |
-| [organization](variables.tf#L59) | Apigee organization. | object({…}) | | {…} |
-| [path](variables.tf#L75) | Bucket path. | string | | "/analytics" |
-| [project_create](variables.tf#L82) | Parameters for the creation of the new project. | object({…}) | | null |
-| [vpc_create](variables.tf#L103) | Boolean flag indicating whether the VPC should be created or not. | bool | | true |
+| [organization](variables.tf#L60) | Apigee organization. | object({…}) | | {…} |
+| [path](variables.tf#L76) | Bucket path. | string | | "/analytics" |
+| [project_create](variables.tf#L83) | Parameters for the creation of the new project. | object({…}) | | null |
+| [vpc_create](variables.tf#L104) | Boolean flag indicating whether the VPC should be created or not. | bool | | true |
## Outputs
diff --git a/blueprints/cloud-operations/apigee/diagram1.png b/blueprints/apigee/bigquery-analytics/diagram1.png
similarity index 100%
rename from blueprints/cloud-operations/apigee/diagram1.png
rename to blueprints/apigee/bigquery-analytics/diagram1.png
diff --git a/blueprints/cloud-operations/apigee/diagram2.png b/blueprints/apigee/bigquery-analytics/diagram2.png
similarity index 100%
rename from blueprints/cloud-operations/apigee/diagram2.png
rename to blueprints/apigee/bigquery-analytics/diagram2.png
diff --git a/blueprints/cloud-operations/apigee/functions/export/index.js b/blueprints/apigee/bigquery-analytics/functions/export/index.js
similarity index 100%
rename from blueprints/cloud-operations/apigee/functions/export/index.js
rename to blueprints/apigee/bigquery-analytics/functions/export/index.js
diff --git a/blueprints/cloud-operations/apigee/functions/export/package.json b/blueprints/apigee/bigquery-analytics/functions/export/package.json
similarity index 100%
rename from blueprints/cloud-operations/apigee/functions/export/package.json
rename to blueprints/apigee/bigquery-analytics/functions/export/package.json
diff --git a/blueprints/cloud-operations/apigee/functions/gcs2bq/index.js b/blueprints/apigee/bigquery-analytics/functions/gcs2bq/index.js
similarity index 100%
rename from blueprints/cloud-operations/apigee/functions/gcs2bq/index.js
rename to blueprints/apigee/bigquery-analytics/functions/gcs2bq/index.js
diff --git a/blueprints/cloud-operations/apigee/functions/gcs2bq/package.json b/blueprints/apigee/bigquery-analytics/functions/gcs2bq/package.json
similarity index 100%
rename from blueprints/cloud-operations/apigee/functions/gcs2bq/package.json
rename to blueprints/apigee/bigquery-analytics/functions/gcs2bq/package.json
diff --git a/blueprints/cloud-operations/apigee/functions/gcs2bq/schema.json b/blueprints/apigee/bigquery-analytics/functions/gcs2bq/schema.json
similarity index 100%
rename from blueprints/cloud-operations/apigee/functions/gcs2bq/schema.json
rename to blueprints/apigee/bigquery-analytics/functions/gcs2bq/schema.json
diff --git a/blueprints/cloud-operations/apigee/main.tf b/blueprints/apigee/bigquery-analytics/main.tf
similarity index 94%
rename from blueprints/cloud-operations/apigee/main.tf
rename to blueprints/apigee/bigquery-analytics/main.tf
index 9781a4de4..68e672d25 100644
--- a/blueprints/cloud-operations/apigee/main.tf
+++ b/blueprints/apigee/bigquery-analytics/main.tf
@@ -68,9 +68,12 @@ module "vpc" {
region = k
}]
psa_config = {
- ranges = {
- for k, v in var.instances : "apigee-${k}" => v.psa_ip_cidr_range
- }
+ ranges = merge({ for k, v in var.instances :
+ "apigee-runtime-${k}" => v.runtime_ip_cidr_range
+ }, { for k, v in var.instances :
+ "apigee-troubleshooting-${k}" => v.troubleshooting_ip_cidr_range
+ }
+ )
}
}
@@ -94,8 +97,9 @@ module "glb" {
use_classic_version = false
backend_service_configs = {
default = {
- backends = [for k, v in var.instances : { backend = k }]
- protocol = "HTTPS"
+ backends = [for k, v in var.instances : { backend = k }]
+ protocol = "HTTPS"
+ health_checks = []
}
}
health_check_configs = {
@@ -116,8 +120,10 @@ module "glb" {
}
}
ssl_certificates = {
- managed_config = {
- for k, v in var.envgroups : k => { domains = [v] }
+ managed_configs = {
+ default = {
+ domains = flatten([for k, v in var.envgroups : v])
+ }
}
}
}
diff --git a/blueprints/cloud-operations/apigee/outputs.tf b/blueprints/apigee/bigquery-analytics/outputs.tf
similarity index 100%
rename from blueprints/cloud-operations/apigee/outputs.tf
rename to blueprints/apigee/bigquery-analytics/outputs.tf
diff --git a/blueprints/cloud-operations/apigee/send-requests.sh b/blueprints/apigee/bigquery-analytics/send-requests.sh
similarity index 100%
rename from blueprints/cloud-operations/apigee/send-requests.sh
rename to blueprints/apigee/bigquery-analytics/send-requests.sh
diff --git a/blueprints/cloud-operations/apigee/templates/create-datastore.sh.tpl b/blueprints/apigee/bigquery-analytics/templates/create-datastore.sh.tpl
similarity index 100%
rename from blueprints/cloud-operations/apigee/templates/create-datastore.sh.tpl
rename to blueprints/apigee/bigquery-analytics/templates/create-datastore.sh.tpl
diff --git a/blueprints/cloud-operations/apigee/templates/deploy-apiproxy.sh.tpl b/blueprints/apigee/bigquery-analytics/templates/deploy-apiproxy.sh.tpl
similarity index 100%
rename from blueprints/cloud-operations/apigee/templates/deploy-apiproxy.sh.tpl
rename to blueprints/apigee/bigquery-analytics/templates/deploy-apiproxy.sh.tpl
diff --git a/tests/blueprints/cloud_operations/apigee/fixture/test.regular.tfvars b/blueprints/apigee/bigquery-analytics/terraform.tfvars.sample
similarity index 66%
rename from tests/blueprints/cloud_operations/apigee/fixture/test.regular.tfvars
rename to blueprints/apigee/bigquery-analytics/terraform.tfvars.sample
index 867cdb119..5a25a9f37 100644
--- a/tests/blueprints/cloud_operations/apigee/fixture/test.regular.tfvars
+++ b/blueprints/apigee/bigquery-analytics/terraform.tfvars.sample
@@ -1,10 +1,10 @@
project_create = {
- billing_account_id = "12345-12345-12345"
+ billing_account_id = "12345-12345-123456"
parent = "folders/123456789"
}
project_id = "my-project"
envgroups = {
- test = ["test.cool-demos.space"]
+ test = ["test.myorg.org"]
}
environments = {
apis-test = {
@@ -15,7 +15,8 @@ instances = {
instance-ew1 = {
region = "europe-west1"
environments = ["apis-test"]
- psa_ip_cidr_range = "10.0.4.0/22"
+ runtime_ip_cidr_range = "10.0.4.0/22"
+ troubleshooting_ip_cidr_range = "10.1.1.0/28"
}
}
psc_config = {
diff --git a/blueprints/cloud-operations/apigee/variables.tf b/blueprints/apigee/bigquery-analytics/variables.tf
similarity index 86%
rename from blueprints/cloud-operations/apigee/variables.tf
rename to blueprints/apigee/bigquery-analytics/variables.tf
index ba7f5d78a..1bd6cb0ac 100644
--- a/blueprints/cloud-operations/apigee/variables.tf
+++ b/blueprints/apigee/bigquery-analytics/variables.tf
@@ -45,13 +45,14 @@ variable "environments" {
variable "instances" {
description = "Instance."
type = map(object({
- display_name = optional(string)
- description = optional(string)
- region = string
- environments = list(string)
- psa_ip_cidr_range = string
- disk_encryption_key = optional(string)
- consumer_accept_list = optional(list(string))
+ display_name = optional(string)
+ description = optional(string)
+ region = string
+ environments = list(string)
+ runtime_ip_cidr_range = string
+ troubleshooting_ip_cidr_range = string
+ disk_encryption_key = optional(string)
+ consumer_accept_list = optional(list(string))
}))
nullable = false
}
diff --git a/blueprints/networking/onprem-google-access-dns/versions.tf b/blueprints/apigee/bigquery-analytics/versions.tf
similarity index 91%
rename from blueprints/networking/onprem-google-access-dns/versions.tf
rename to blueprints/apigee/bigquery-analytics/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/networking/onprem-google-access-dns/versions.tf
+++ b/blueprints/apigee/bigquery-analytics/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/apigee/hybrid-gke/README.md b/blueprints/apigee/hybrid-gke/README.md
new file mode 100644
index 000000000..ae5c03648
--- /dev/null
+++ b/blueprints/apigee/hybrid-gke/README.md
@@ -0,0 +1,69 @@
+# Apigee Hybrid on GKE
+
+This example installs Apigee hybrid in a non-prod environment on a GKE private cluster using Terraform and Ansible.
+The Terraform configuration deploys all the required infrastructure including a management VM used to run an ansible playbook to the actual Apigee Hybrid setup.
+
+The diagram below depicts the architecture.
+
+
+
+## Running the blueprint
+
+1. Clone this repository or [open it in cloud shell](https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2Fterraform-google-modules%2Fcloud-foundation-fabric&cloudshell_print=cloud-shell-readme.txt&cloudshell_working_dir=blueprints%2Fapigee%2Fhybrid), then go through the following steps to create resources:
+
+2. Copy the file [terraform.tfvars.sample](./terraform.tfvars.sample) to a file called ```terraform.tfvars``` and update the values if required.
+
+3. Initialize the terraform configuration
+
+ ```
+ terraform init
+ ```
+
+4. Apply the terraform configuration
+
+ ```
+ terraform apply
+ ```
+
+ Create an A record in your DNS registrar to point the environment group hostname to the public IP address returned after the terraform configuration was applied. You might need to wait some time until the certificate is provisioned.
+
+5. Install Apigee hybrid using de ansible playbook that is in the ansible folder by running this command
+
+ ansible-playbook playbook.yaml -vvvß
+
+## Testing the blueprint
+
+2. Deploy an api proxy
+
+ ```
+ ./deploy-apiproxy.sh apis-test
+ ```
+
+3. Send a request
+
+ ```
+ curl -v https://HOSTNAME/httpbin/headers
+ ```
+
+
+## Variables
+
+| name | description | type | required | default |
+|---|---|:---:|:---:|:---:|
+| [hostname](variables.tf#L43) | Host name. | string | ✓ | |
+| [project_id](variables.tf#L79) | Project ID. | string | ✓ | |
+| [cluster_machine_type](variables.tf#L17) | Cluster nachine type. | string | | "e2-standard-4" |
+| [cluster_network_config](variables.tf#L23) | Cluster network configuration. | object({…}) | | {…} |
+| [mgmt_server_config](variables.tf#L48) | Mgmt server configuration. | object({…}) | | {…} |
+| [mgmt_subnet_cidr_block](variables.tf#L64) | Management subnet CIDR block. | string | | "10.0.2.0/28" |
+| [project_create](variables.tf#L70) | Parameters for the creation of the new project. | object({…}) | | null |
+| [region](variables.tf#L84) | Region. | string | | "europe-west1" |
+| [zone](variables.tf#L90) | Zone. | string | | "europe-west1-c" |
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| [ip_address](outputs.tf#L17) | GLB IP address. | |
+
+
diff --git a/blueprints/apigee/hybrid-gke/ansible.tf b/blueprints/apigee/hybrid-gke/ansible.tf
new file mode 100644
index 000000000..b7694ab1f
--- /dev/null
+++ b/blueprints/apigee/hybrid-gke/ansible.tf
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2023 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 Ansible generated files.
+
+resource "local_file" "vars_file" {
+ content = yamlencode({
+ cluster = module.cluster.name
+ region = var.region
+ project_id = module.project.project_id
+ envgroups = local.envgroups
+ environments = local.environments
+ service_accounts = local.google_sas
+ ingress_ip_name = local.ingress_ip_name
+ })
+ filename = "${path.module}/ansible/vars/vars.yaml"
+ file_permission = "0666"
+}
+
+resource "local_file" "gssh_file" {
+ content = templatefile("${path.module}/templates/gssh.sh.tpl", {
+ project_id = module.project.project_id
+ zone = var.zone
+ })
+ filename = "${path.module}/ansible/gssh.sh"
+ file_permission = "0777"
+}
diff --git a/blueprints/apigee/hybrid-gke/ansible/ansible.cfg b/blueprints/apigee/hybrid-gke/ansible/ansible.cfg
new file mode 100644
index 000000000..654f1729d
--- /dev/null
+++ b/blueprints/apigee/hybrid-gke/ansible/ansible.cfg
@@ -0,0 +1,8 @@
+[defaults]
+inventory = inventory/hosts.ini
+timeout = 900
+
+[ssh_connection]
+pipelining = True
+ssh_executable = ./gssh.sh
+transfer_method = piped
\ No newline at end of file
diff --git a/blueprints/apigee/hybrid-gke/ansible/inventory/hosts.ini b/blueprints/apigee/hybrid-gke/ansible/inventory/hosts.ini
new file mode 100644
index 000000000..842da83f4
--- /dev/null
+++ b/blueprints/apigee/hybrid-gke/ansible/inventory/hosts.ini
@@ -0,0 +1 @@
+mgmt
\ No newline at end of file
diff --git a/blueprints/apigee/hybrid-gke/ansible/playbook.yaml b/blueprints/apigee/hybrid-gke/ansible/playbook.yaml
new file mode 100644
index 000000000..1daa4d86a
--- /dev/null
+++ b/blueprints/apigee/hybrid-gke/ansible/playbook.yaml
@@ -0,0 +1,26 @@
+# 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.
+
+- hosts: mgmt
+ gather_facts: "no"
+ vars_files:
+ - vars/vars.yaml
+ environment:
+ USE_GKE_GCLOUD_AUTH_PLUGIN: True
+ roles:
+ - role: prerequisites
+ become: yes
+ become_method: sudo
+ - role: apigee-hybrid
+
\ No newline at end of file
diff --git a/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/k8s_service_accounts.yaml b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/k8s_service_accounts.yaml
new file mode 100644
index 000000000..e74ca1596
--- /dev/null
+++ b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/k8s_service_accounts.yaml
@@ -0,0 +1,28 @@
+# Copyright 2023 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.
+
+- name: Create and annotate k8s service account
+ kubernetes.core.k8s:
+ state: present
+ definition:
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ name: "{{ k8s_service_account }}"
+ namespace: apigee
+ annotations:
+ iam.gke.io/gcp-service-account: "{{ google_service_account }}@{{ project_id }}.iam.gserviceaccount.com"
+ with_items: "{{ k8s_service_accounts }}"
+ loop_control:
+ loop_var: k8s_service_account
\ No newline at end of file
diff --git a/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/main.yaml b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/main.yaml
new file mode 100644
index 000000000..0907846fd
--- /dev/null
+++ b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/main.yaml
@@ -0,0 +1,333 @@
+# Copyright 2023 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.
+
+- name: Get cluster credentials
+ shell: >
+ gcloud container clusters get-credentials {{ cluster }} \
+ --region {{ region }} \
+ --project {{ project_id }} \
+ --internal-ip
+
+- name: Download cert-manager
+ uri:
+ url: https://github.com/jetstack/cert-manager/releases/download/v1.7.2/cert-manager.yaml
+ dest: ~/cert-manager.yaml
+
+- name: Apply metrics-server manifest to the cluster.
+ kubernetes.core.k8s:
+ state: present
+ src: ~/cert-manager.yaml
+
+- name:
+ kubernetes.core.k8s_info:
+ kind: Pod
+ wait: yes
+ label_selectors:
+ - "app.kubernetes.io/instance=cert-manager"
+ namespace: cert-manager
+ wait_timeout: 90
+ wait_condition:
+ type: Ready
+ status: True
+
+- name: Fetch apigeectl version
+ uri:
+ url: https://storage.googleapis.com/apigee-release/hybrid/apigee-hybrid-setup/current-version.txt?ignoreCache=1
+ return_content: yes
+ register: version
+
+- name: Download apigeectl bundle
+ uri:
+ url: https://storage.googleapis.com/apigee-release/hybrid/apigee-hybrid-setup/{{ version.content }}/apigeectl_linux_64.tar.gz
+ dest: "~/apigeectl.tar.gz"
+ status_code: [200, 304]
+
+- name: Extract apigeectl bundle
+ unarchive:
+ src: "~/apigeectl.tar.gz"
+ dest: "~"
+ remote_src: yes
+
+- name: Move apigeectl folder
+ shell: >
+ mv ~/apigeectl_* ~/apigeectl
+
+- name: Create hybrid-files
+ file:
+ path: "~/hybrid-files/{{ item }}"
+ state: directory
+ with_items:
+ - overrides
+ - certs
+
+- name: Create a symbolic links
+ file:
+ src: ~/apigeectl/{{ item }}
+ dest: "~/hybrid-files/{{ item }}"
+ state: link
+ with_items:
+ - tools
+ - config
+ - templates
+ - plugins
+
+- name: Create apigee namespace
+ kubernetes.core.k8s:
+ state: present
+ definition:
+ apiVersion: v1
+ kind: Namespace
+ metadata:
+ name: apigee
+
+- name: Create k8s service accounts
+ include_tasks: k8s_service_accounts.yaml
+ vars:
+ google_service_account: "{{ item.key }}"
+ k8s_service_accounts: "{{ item.value }}"
+ with_dict: "{{ service_accounts }}"
+
+- name: Set hostnames
+ set_fact:
+ hostnames: "{{ hostnames | default([]) + item.value }}"
+ with_dict: "{{ envgroups }}"
+
+- name: Create certificate and private key
+ shell: >
+ openssl req \
+ -nodes \
+ -new \
+ -x509 \
+ -keyout ~/hybrid-files/certs/server.key \
+ -out ~/hybrid-files/certs/server.crt \
+ -subj "/CN=apigee.com' \
+ -addext "subjectAltName={{ hostnames | map('regex_replace', '^', 'DNS:') | join(',') }}""
+ -days 3650
+
+- name: Read certificate
+ slurp:
+ src: ~/hybrid-files/certs/server.crt
+ register: certificate_output
+
+- name: Read private ket
+ slurp:
+ src: ~/hybrid-files/certs/server.key
+ register: privatekey_output
+
+- name: Create secret
+ kubernetes.core.k8s:
+ state: present
+ definition:
+ apiVersion: v1
+ kind: Secret
+ metadata:
+ name: tls-hybrid-ingress
+ namespace: apigee
+ type: kubernetes.io/tls
+ data:
+ tls.crt: "{{ certificate_output.content }}"
+ tls.key: "{{ privatekey_output.content }}"
+
+- name: Create overrides.yaml
+ template:
+ src: templates/overrides.yaml.j2
+ dest: ~/hybrid-files/overrides/overrides.yaml
+
+- name: Enable syncronizer access
+ shell: >
+ curl -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" \
+ -H "Content-Type:application/json" \
+ "https://apigee.googleapis.com/v1/organizations/{{ project_id }}:setSyncAuthorization" \
+ -d '{"identities":["'"serviceAccount:apigee-synchronizer@{{ project_id }}.iam.gserviceaccount.com"'"]}'
+
+- name: Dry-run (init)
+ shell: >
+ ~/apigeectl/apigeectl init -f overrides/overrides.yaml --dry-run=client
+ args:
+ chdir: ~/hybrid-files
+
+- name: Install the Apigee deployment services Apigee Deployment Controller and Apigee Admission Webhook.
+ shell: >
+ ~/apigeectl/apigeectl init -f overrides/overrides.yaml
+ args:
+ chdir: ~/hybrid-files
+
+- name: Wait for apigee-controller pod to be ready
+ kubernetes.core.k8s_info:
+ kind: Pod
+ wait: yes
+ label_selectors:
+ - "app=apigee-controller"
+ namespace: apigee-system
+ wait_timeout: 600
+ wait_condition:
+ type: Ready
+ status: True
+
+- name: Wait for apigee-selfsigned-issuer issuer to be ready
+ kubernetes.core.k8s_info:
+ kind: Issuer
+ wait: yes
+ name: apigee-selfsigned-issuer
+ namespace: apigee-system
+ wait_timeout: 600
+ wait_condition:
+ type: Ready
+ status: True
+
+- name: Wait for apigee-serving-cert certificate to be ready
+ kubernetes.core.k8s_info:
+ kind: Certificate
+ wait: yes
+ name: apigee-serving-cert
+ namespace: apigee-system
+ wait_timeout: 600
+ wait_condition:
+ type: Ready
+ status: True
+
+- name: Wait for apigee-resources-install job to be complete
+ kubernetes.core.k8s_info:
+ kind: Job
+ wait: yes
+ name: apigee-resources-install
+ namespace: apigee-system
+ wait_timeout: 360
+ wait_condition:
+ type: Complete
+ status: True
+
+- name: Dry-run (apply)
+ shell: >
+ ~/apigeectl/apigeectl apply -f overrides/overrides.yaml --dry-run=client
+ args:
+ chdir: ~/hybrid-files
+
+- name: Install the Apigee runtime components
+ shell: >
+ ~/apigeectl/apigeectl apply -f overrides/overrides.yaml
+ args:
+ chdir: ~/hybrid-files
+
+- name: Wait for apigee-runtime pod to be ready
+ kubernetes.core.k8s_info:
+ kind: Pod
+ wait: yes
+ label_selectors:
+ - "app=apigee-runtime"
+ namespace: apigee
+ wait_timeout: 360
+ wait_condition:
+ type: Ready
+ status: True
+
+- name:
+ kubernetes.core.k8s:
+ state: present
+ definition:
+ apiVersion: apigee.cloud.google.com/v1alpha1
+ kind: ApigeeRoute
+ metadata:
+ name: apigee-wildcard
+ namespace: apigee
+ spec:
+ hostnames:
+ - '*'
+ ports:
+ - number: 443
+ protocol: HTTPS
+ tls:
+ credentialName: tls-hybrid-ingress
+ mode: SIMPLE
+ selector:
+ app: apigee-ingressgateway
+ enableNonSniClient: true
+
+- name: Create google-managed certificate
+ kubernetes.core.k8s:
+ state: present
+ definition:
+ apiVersion: networking.gke.io/v1
+ kind: ManagedCertificate
+ metadata:
+ name: "apigee-cert-hybrid"
+ namespace: apigee
+ spec:
+ domains: "{{ hostnames }}"
+
+- name: Create backend config
+ kubernetes.core.k8s:
+ state: present
+ definition:
+ apiVersion: cloud.google.com/v1
+ kind: BackendConfig
+ metadata:
+ name: apigee-ingress-backendconfig
+ namespace: apigee
+ spec:
+ healthCheck:
+ requestPath: /healthz/ready
+ port: 15021
+ type: HTTP
+ logging:
+ enable: true
+ sampleRate: 0.5
+
+- name: Create service
+ kubernetes.core.k8s:
+ state: present
+ definition:
+ apiVersion: v1
+ kind: Service
+ metadata:
+ name: apigee-ingressgateway-hybrid
+ namespace: apigee
+ annotations:
+ cloud.google.com/backend-config: '{"default": "apigee-ingress-backendconfig"}'
+ cloud.google.com/neg: '{"ingress": true}'
+ cloud.google.com/app-protocols: '{"https":"HTTPS", "status-port": "HTTP"}'
+ labels:
+ app: apigee-ingressgateway-hybrid
+ spec:
+ ports:
+ - name: status-port
+ port: 15021
+ targetPort: 15021
+ - name: https
+ port: 443
+ targetPort: 8443
+ selector:
+ app: apigee-ingressgateway
+ ingress_name: ingress
+ type: ClusterIP
+
+- name: Create ingress
+ kubernetes.core.k8s:
+ state: present
+ definition:
+ apiVersion: networking.k8s.io/v1
+ kind: Ingress
+ metadata:
+ annotations:
+ networking.gke.io/managed-certificates: "apigee-cert-hybrid"
+ kubernetes.io/ingress.global-static-ip-name: "{{ ingress_ip_name }}"
+ kubernetes.io/ingress.allow-http: "false"
+ name: xlb-apigee
+ namespace: apigee
+ spec:
+ defaultBackend:
+ service:
+ name: apigee-ingressgateway-hybrid
+ port:
+ number: 443
\ No newline at end of file
diff --git a/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/templates/overrides.yaml.j2 b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/templates/overrides.yaml.j2
new file mode 100644
index 000000000..691cc6d5d
--- /dev/null
+++ b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/templates/overrides.yaml.j2
@@ -0,0 +1,42 @@
+gcp:
+ region: {{ region }}
+ projectID: {{ project_id }}
+ workloadIdentityEnabled: true
+
+k8sCluster:
+ name: {{ cluster }}
+ region: {{ region }} # Must be the closest Google Cloud region to your cluster.
+org: {{ project_id }}
+
+instanceID: "{{ cluster }}-{{ region }}"
+
+cassandra:
+ hostNetwork: false
+
+virtualhosts:
+{% for k in envgroups %}
+ - name: {{ k }}
+ sslSecret: tls-hybrid-ingress
+ additionalGateways: ["apigee-wildcard"]
+ selector:
+ app: apigee-ingressgateway
+{% endfor %}
+
+ao:
+ args:
+ # This configuration is introduced in hybrid v1.8
+ disableIstioConfigInAPIServer: true
+
+# This configuration is introduced in hybrid v1.8
+ingressGateways:
+- name: ingress # maximum 17 characters. See Known issue 243167389.
+ replicaCountMin: 2
+ replicaCountMax: 10
+
+envs:
+{% for k in environments %}
+ - name: {{ k }}
+{% endfor %}
+
+logger:
+ enabled: false
diff --git a/blueprints/apigee/hybrid-gke/ansible/roles/prerequisites/tasks/main.yaml b/blueprints/apigee/hybrid-gke/ansible/roles/prerequisites/tasks/main.yaml
new file mode 100644
index 000000000..b438a6342
--- /dev/null
+++ b/blueprints/apigee/hybrid-gke/ansible/roles/prerequisites/tasks/main.yaml
@@ -0,0 +1,37 @@
+# Copyright 2021 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.
+
+- name: Download the Google Cloud SDK package repository signing key
+ get_url:
+ url: https://packages.cloud.google.com/apt/doc/apt-key.gpg
+ dest: /usr/share/keyrings/cloud.google.gpg
+
+- name: Add Google Cloud SDK package repository source
+ apt_repository:
+ filename: google-cloud-sdk.list
+ repo: "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main"
+ state: present
+ update_cache: yes
+
+- name: Install dependencies
+ apt:
+ pkg:
+ - kubectl
+ - google-cloud-sdk-gke-gcloud-auth-plugin
+ state: present
+
+- name: Install gke-gcloud-auth-plugin
+ apt:
+ name: google-cloud-sdk-gke-gcloud-auth-plugin
+ state: present
\ No newline at end of file
diff --git a/blueprints/apigee/hybrid-gke/apigee.tf b/blueprints/apigee/hybrid-gke/apigee.tf
new file mode 100644
index 000000000..b92592aab
--- /dev/null
+++ b/blueprints/apigee/hybrid-gke/apigee.tf
@@ -0,0 +1,93 @@
+/**
+ * Copyright 2023 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 {
+ envgroups = {
+ test = [var.hostname]
+ }
+ environments = {
+ apis-test = {
+ envgroups = ["test"]
+ }
+ }
+ org_short_name = (length(module.project.project_id) < 16 ?
+ module.project.project_id :
+ substr(module.project.project_id, 0, 15))
+ org_hash = format("%s-%s", local.org_short_name, substr(sha256(module.project.project_id), 0, 7))
+ org_env_hashes = {
+ for k, v in local.environments :
+ k => format("%s-%s-%s", local.org_short_name, length(k) < 16 ? k : substr(k, 0, 15), substr(sha256("${module.project.project_id}:${k}"), 0, 7))
+ }
+ google_sas = {
+ apigee-metrics = [
+ "apigee-metrics-sa"
+ ]
+ apigee-cassandra = [
+ "apigee-cassandra-schema-setup-${local.org_hash}-sa",
+ "apigee-cassandra-user-setup-${local.org_hash}-sa"
+ ]
+ apigee-mart = [
+ "apigee-mart-${local.org_hash}-sa",
+ "apigee-connect-agent-${local.org_hash}-sa"
+ ]
+ apigee-watcher = [
+ "apigee-watcher-${local.org_hash}-sa"
+ ]
+ apigee-udca = concat([
+ "apigee-udca-${local.org_hash}-sa"
+ ],
+ [for k, v in local.org_env_hashes :
+ "apigee-udca-${local.org_env_hashes[k]}-sa"
+ ])
+ apigee-synchronizer = [
+ for k, v in local.org_env_hashes :
+ "apigee-synchronizer-${local.org_env_hashes[k]}-sa"
+ ]
+ apigee-runtime = [for k, v in local.org_env_hashes :
+ "apigee-runtime-${local.org_env_hashes[k]}-sa"
+ ]
+ }
+}
+
+module "apigee" {
+ source = "../../../modules/apigee"
+ project_id = module.project.project_id
+ organization = {
+ analytics_region = var.region
+ runtime_type = "HYBRID"
+ }
+ envgroups = local.envgroups
+ environments = local.environments
+}
+
+module "sas" {
+ for_each = local.google_sas
+ source = "../../../modules/iam-service-account"
+ project_id = module.project.project_id
+ name = each.key
+ # authoritative roles granted *on* the service accounts to other identities
+ iam = {
+ "roles/iam.workloadIdentityUser" = [for v in each.value : "serviceAccount:${module.project.project_id}.svc.id.goog[apigee/${v}]"]
+ }
+}
+
+resource "local_file" "deploy_apiproxy_file" {
+ content = templatefile("${path.module}/templates/deploy-apiproxy.sh.tpl", {
+ org = module.project.project_id
+ })
+ filename = "${path.module}/deploy-apiproxy.sh"
+ file_permission = "0777"
+}
diff --git a/blueprints/apigee/hybrid-gke/diagram.png b/blueprints/apigee/hybrid-gke/diagram.png
new file mode 100644
index 000000000..57e07ca30
Binary files /dev/null and b/blueprints/apigee/hybrid-gke/diagram.png differ
diff --git a/blueprints/apigee/hybrid-gke/gke.tf b/blueprints/apigee/hybrid-gke/gke.tf
new file mode 100644
index 000000000..22cf06fab
--- /dev/null
+++ b/blueprints/apigee/hybrid-gke/gke.tf
@@ -0,0 +1,82 @@
+/**
+ * Copyright 2023 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 "cluster" {
+ source = "../../../modules/gke-cluster"
+ project_id = module.project.project_id
+ name = "cluster"
+ location = var.region
+ vpc_config = {
+ network = module.vpc.self_link
+ subnetwork = module.vpc.subnet_self_links["${var.region}/subnet-apigee"]
+ secondary_range_names = {
+ pods = "pods"
+ services = "services"
+ }
+ master_authorized_ranges = var.cluster_network_config.master_authorized_cidr_blocks
+ master_ipv4_cidr_block = var.cluster_network_config.master_cidr_block
+ }
+ max_pods_per_node = 32
+ private_cluster_config = {
+ enable_private_endpoint = true
+ master_global_access = false
+ }
+ enable_features = {
+ workload_identity = true
+ }
+}
+
+module "apigee-data-nodepool" {
+ source = "../../../modules/gke-nodepool"
+ project_id = module.project.project_id
+ cluster_name = module.cluster.name
+ location = var.region
+ name = "apigee-data-nodepool"
+ nodepool_config = {
+ autoscaling = {
+ min_node_count = 1
+ max_node_count = 3
+ }
+ }
+ node_config = {
+ machine_type = var.cluster_machine_type
+ }
+ service_account = {
+ create = true
+ }
+ tags = ["node"]
+}
+
+module "apigee-runtime-nodepool" {
+ source = "../../../modules/gke-nodepool"
+ project_id = module.project.project_id
+ cluster_name = module.cluster.name
+ location = var.region
+ name = "apigee-runtime-nodepool"
+ nodepool_config = {
+ autoscaling = {
+ min_node_count = 1
+ max_node_count = 3
+ }
+ }
+ node_config = {
+ machine_type = var.cluster_machine_type
+ }
+ service_account = {
+ create = true
+ }
+ tags = ["node"]
+}
\ No newline at end of file
diff --git a/tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/variables.tf b/blueprints/apigee/hybrid-gke/glb.tf
similarity index 63%
rename from tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/variables.tf
rename to blueprints/apigee/hybrid-gke/glb.tf
index 6a534739f..80ff2269c 100644
--- a/tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/variables.tf
+++ b/blueprints/apigee/hybrid-gke/glb.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-variable "billing_account" {
- type = string
- default = "123456-123456-123456"
+locals {
+ ingress_ip_name = "apigee"
}
-variable "root_node" {
- description = "The resource name of the parent Folder or Organization. Must be of the form folders/folder_id or organizations/org_id."
- type = string
- default = "folders/12345678"
+module "addresses" {
+ source = "../../../modules/net-address"
+ project_id = module.project.project_id
+ global_addresses = [local.ingress_ip_name]
}
diff --git a/blueprints/apigee/hybrid-gke/main.tf b/blueprints/apigee/hybrid-gke/main.tf
new file mode 100644
index 000000000..5be174ef5
--- /dev/null
+++ b/blueprints/apigee/hybrid-gke/main.tf
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2023 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 "project" {
+ source = "../../../modules/project"
+ billing_account = (var.project_create != null
+ ? var.project_create.billing_account_id
+ : null
+ )
+ parent = (var.project_create != null
+ ? var.project_create.parent
+ : null
+ )
+ project_create = var.project_create != null
+ name = var.project_id
+ services = [
+ "apigee.googleapis.com",
+ "apigeeconnect.googleapis.com",
+ "cloudresourcemanager.googleapis.com",
+ "compute.googleapis.com",
+ "container.googleapis.com",
+ "pubsub.googleapis.com"
+ ]
+ iam = {
+ "roles/apigee.admin" = [module.mgmt_server.service_account_iam_email]
+ "roles/container.admin" = [module.mgmt_server.service_account_iam_email]
+ "roles/resourcemanager.projectIamAdmin" = [module.mgmt_server.service_account_iam_email]
+ "roles/iam.serviceAccountAdmin" = [module.mgmt_server.service_account_iam_email]
+ "roles/iam.serviceAccountKeyAdmin" = [module.mgmt_server.service_account_iam_email]
+ "roles/monitoring.metricWriter" = [module.sas["apigee-metrics"].iam_email]
+ "roles/storage.objectAdmin" = [module.sas["apigee-cassandra"].iam_email]
+ "roles/apigeeconnect.Agent" = [module.sas["apigee-mart"].iam_email]
+ "roles/apigee.runtimeAgent" = [module.sas["apigee-watcher"].iam_email]
+ "roles/apigee.analyticsAgent" = [module.sas["apigee-udca"].iam_email]
+ "roles/apigee.synchronizerManager" = [module.sas["apigee-synchronizer"].iam_email]
+ "roles/cloudtrace.agent" = [module.sas["apigee-runtime"].iam_email]
+ }
+}
diff --git a/blueprints/apigee/hybrid-gke/mgmt.tf b/blueprints/apigee/hybrid-gke/mgmt.tf
new file mode 100644
index 000000000..538940e7b
--- /dev/null
+++ b/blueprints/apigee/hybrid-gke/mgmt.tf
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2023 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 Management server.
+
+module "mgmt_server" {
+ source = "../../../modules/compute-vm"
+ project_id = module.project.project_id
+ zone = var.zone
+ name = "mgmt"
+ instance_type = var.mgmt_server_config.instance_type
+ network_interfaces = [{
+ network = module.vpc.self_link
+ subnetwork = module.vpc.subnet_self_links["${var.region}/subnet-mgmt"]
+ nat = false
+ addresses = null
+ }]
+ service_account_create = true
+ boot_disk = {
+ image = var.mgmt_server_config.image
+ type = var.mgmt_server_config.disk_type
+ size = var.mgmt_server_config.disk_size
+ }
+ metadata = {
+ startup-script = <
This [blueprint](./nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/) shows how to expose an on-prem target backend to clients in the Internet.g
\ No newline at end of file
diff --git a/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/README.md b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/README.md
new file mode 100644
index 000000000..690458f03
--- /dev/null
+++ b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/README.md
@@ -0,0 +1,69 @@
+# Apigee X - Northbound GLB with PSC Neg, Southbouth PSC with ILB (L7) and Hybrid NEG
+
+The following blueprint shows how to expose an on-prem target backend to clients in the Internet.
+
+The architecture is the one depicted below.
+
+
+
+To emulate an service deployed on-premise, we have used a managed instance group of instances running Nginx exposed via a regional internalload balancer (L7). The service is accesible through VPN.
+
+## Running the blueprint
+
+1. Clone this repository or [open it in cloud shell](https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2Fterraform-google-modules%2Fcloud-foundation-fabric&cloudshell_print=cloud-shell-readme.txt&cloudshell_working_dir=blueprints%2F%apigee%2F/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg), then go through the following steps to create resources:
+
+2. Copy the file [terraform.tfvars.sample](./terraform.tfvars.sample) to a file called ```terraform.tfvars``` and update the values if required.
+
+3. Initialize the terraform configuration
+
+ ```terraform init```
+
+4. Apply the terraform configuration
+
+ ```terraform apply```
+
+Once the resources have been created, do the following:
+
+Create an A record in your DNS registrar to point the environment group hostname to the public IP address returned after the terraform configuration was applied. You might need to wait some time until the certificate is provisioned.
+
+## Testing the blueprint
+
+Do the following to verify that everything works as expected.
+
+1. Deploy the API proxy
+
+ ./deploy-apiproxy.sh
+
+2. Send a request
+
+ curl -v https://HOSTNAME/test/
+
+ You should get back an HTTP 200 OK response.
+
+
+## Variables
+
+| name | description | type | required | default |
+|---|---|:---:|:---:|:---:|
+| [apigee_project_id](variables.tf#L17) | Project ID. | string | ✓ | |
+| [billing_account_id](variables.tf#L53) | Parameters for the creation of the new project. | string | ✓ | |
+| [hostname](variables.tf#L58) | Host name. | string | ✓ | |
+| [onprem_project_id](variables.tf#L63) | Project ID. | string | ✓ | |
+| [parent](variables.tf#L81) | Parent (organizations/organizationID or folders/folderID). | string | ✓ | |
+| [apigee_proxy_only_subnet_ip_cidr_range](variables.tf#L23) | Subnet IP CIDR range. | string | | "10.2.1.0/24" |
+| [apigee_psc_subnet_ip_cidr_range](variables.tf#L29) | Subnet IP CIDR range. | string | | "10.2.2.0/24" |
+| [apigee_runtime_ip_cidr_range](variables.tf#L35) | Apigee PSA IP CIDR range. | string | | "10.0.4.0/22" |
+| [apigee_subnet_ip_cidr_range](variables.tf#L41) | Subnet IP CIDR range. | string | | "10.2.0.0/24" |
+| [apigee_troubleshooting_ip_cidr_range](variables.tf#L47) | Apigee PSA IP CIDR range. | string | | "10.1.0.0/28" |
+| [onprem_proxy_only_subnet_ip_cidr_range](variables.tf#L69) | Subnet IP CIDR range. | string | | "10.1.1.0/24" |
+| [onprem_subnet_ip_cidr_range](variables.tf#L75) | Subnet IP CIDR range. | string | | "10.1.0.0/24" |
+| [region](variables.tf#L86) | Region. | string | | "europe-west1" |
+| [zone](variables.tf#L92) | Zone. | string | | "europe-west1-c" |
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| [ip_address](outputs.tf#L17) | GLB IP address. | |
+
+
diff --git a/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/apigee.tf b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/apigee.tf
new file mode 100644
index 000000000..8860e404c
--- /dev/null
+++ b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/apigee.tf
@@ -0,0 +1,98 @@
+/**
+ * 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 {
+ envgroup = "test"
+ environment = "apis-test"
+}
+
+module "apigee_project" {
+ source = "../../../../modules/project"
+ billing_account = var.billing_account_id
+ parent = var.parent
+ name = var.apigee_project_id
+ services = [
+ "apigee.googleapis.com",
+ "compute.googleapis.com",
+ "servicenetworking.googleapis.com",
+ ]
+}
+
+module "apigee_vpc" {
+ source = "../../../../modules/net-vpc"
+ project_id = module.apigee_project.project_id
+ name = "vpc"
+ subnets_proxy_only = [
+ {
+ ip_cidr_range = var.apigee_proxy_only_subnet_ip_cidr_range
+ name = "regional-proxy"
+ region = var.region
+ active = true
+ }
+ ]
+ subnets = [
+ {
+ ip_cidr_range = var.apigee_subnet_ip_cidr_range
+ name = "subnet"
+ region = var.region
+ }
+ ]
+ subnets_psc = [{
+ ip_cidr_range = var.apigee_psc_subnet_ip_cidr_range
+ name = "subnet-psc"
+ region = var.region
+ }]
+ psa_config = {
+ ranges = {
+ "apigee-runtime" = var.apigee_runtime_ip_cidr_range
+ "apigee-troubleshooting" = var.apigee_troubleshooting_ip_cidr_range
+ }
+ }
+}
+
+module "apigee" {
+ source = "../../../../modules/apigee"
+ project_id = module.apigee_project.project_id
+ organization = {
+ authorized_network = module.apigee_vpc.network.name
+ analytics_region = var.region
+ }
+ envgroups = {
+ (local.envgroup) = [var.hostname]
+ }
+ environments = {
+ (local.environment) = {
+ envgroups = [local.envgroup]
+ }
+ }
+ instances = {
+ instance-1 = {
+ region = var.region
+ environments = [local.environment]
+ runtime_ip_cidr_range = var.apigee_runtime_ip_cidr_range
+ troubleshooting_ip_cidr_range = var.apigee_troubleshooting_ip_cidr_range
+ }
+ }
+ endpoint_attachments = {
+ backend = {
+ region = var.region
+ service_attachment = google_compute_service_attachment.service_attachment.id
+ }
+ }
+ depends_on = [
+ module.apigee_vpc
+ ]
+}
diff --git a/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/apigee_nb.tf b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/apigee_nb.tf
new file mode 100644
index 000000000..b568da9a0
--- /dev/null
+++ b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/apigee_nb.tf
@@ -0,0 +1,50 @@
+/**
+ * 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 "glb" {
+ source = "../../../../modules/net-glb"
+ name = "glb"
+ project_id = module.apigee_project.project_id
+ protocol = "HTTPS"
+ use_classic_version = false
+ backend_service_configs = {
+ default = {
+ backends = [{ backend = "neg-0" }]
+ protocol = "HTTPS"
+ health_checks = []
+ }
+ }
+ neg_configs = {
+ neg-0 = {
+ psc = {
+ region = var.region
+ target_service = module.apigee.instances["instance-1"].service_attachment
+ network = module.apigee_vpc.network.self_link
+ subnetwork = (
+ module.apigee_vpc.subnets_psc["${var.region}/subnet-psc"].self_link
+ )
+ }
+ }
+ }
+ ssl_certificates = {
+ managed_configs = {
+ default = {
+ domains = [var.hostname]
+ }
+ }
+ }
+
+}
diff --git a/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/apigee_sb.tf b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/apigee_sb.tf
new file mode 100644
index 000000000..e6df149b2
--- /dev/null
+++ b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/apigee_sb.tf
@@ -0,0 +1,68 @@
+/**
+ * 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 "apigee_ilb_l7" {
+ source = "../../../../modules/net-ilb-l7"
+ name = "apigee-ilb"
+ project_id = module.apigee_project.project_id
+ region = var.region
+ backend_service_configs = {
+ default = {
+ backends = [{
+ balancing_mode = "RATE"
+ group = "my-neg"
+ max_rate = { per_endpoint = 1 }
+ }]
+ }
+ }
+ neg_configs = {
+ my-neg = {
+ hybrid = {
+ zone = var.zone
+ endpoints = {
+ e-0 = {
+ ip_address = module.onprem_ilb_l7.address
+ port = 80
+ }
+ }
+ }
+ }
+ }
+ health_check_configs = {
+ default = {
+ http = {
+ port = 80
+ }
+ }
+ }
+ vpc_config = {
+ network = module.apigee_vpc.self_link
+ subnetwork = module.apigee_vpc.subnet_self_links["${var.region}/subnet"]
+ }
+ depends_on = [
+ module.apigee_vpc.subnets_proxy_only
+ ]
+}
+
+resource "google_compute_service_attachment" "service_attachment" {
+ name = "service-attachment"
+ project = module.apigee_project.project_id
+ region = var.region
+ enable_proxy_protocol = false
+ connection_preference = "ACCEPT_AUTOMATIC"
+ nat_subnets = [module.apigee_vpc.subnets_psc["${var.region}/subnet-psc"].self_link]
+ target_service = module.apigee_ilb_l7.forwarding_rule.id
+}
diff --git a/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/apiproxy.tf b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/apiproxy.tf
new file mode 100644
index 000000000..a94b11eec
--- /dev/null
+++ b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/apiproxy.tf
@@ -0,0 +1,41 @@
+/**
+ * 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.
+ */
+
+resource "local_file" "target_endpoint_file" {
+ content = templatefile("${path.module}/templates/targets/default.xml.tpl", {
+ ip_address = module.apigee.endpoint_attachment_hosts["backend"]
+ })
+ filename = "${path.module}/bundle/apiproxy/targets/default.xml"
+ file_permission = "0777"
+}
+
+data "archive_file" "bundle" {
+ type = "zip"
+ source_dir = "${path.module}/bundle"
+ output_path = "${path.module}/bundle.zip"
+ depends_on = [
+ local_file.target_endpoint_file
+ ]
+}
+
+resource "local_file" "deploy_apiproxy_file" {
+ content = templatefile("${path.module}/templates/deploy-apiproxy.sh.tpl", {
+ organization = module.apigee.org_name
+ environment = local.environment
+ })
+ filename = "${path.module}/deploy-apiproxy.sh"
+ file_permission = "0777"
+}
diff --git a/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/bundle/apiproxy/proxies/default.xml b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/bundle/apiproxy/proxies/default.xml
new file mode 100644
index 000000000..a277b3cda
--- /dev/null
+++ b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/bundle/apiproxy/proxies/default.xml
@@ -0,0 +1,18 @@
+
+
-Here you see utilization (usage compared to the limit) for a specific metric (number of instances per VPC) for multiple VPCs and projects.
+One other example is the IP utilization information per subnet, allowing you to monitor the percentage of used IP addresses in your GCP subnets.
-Three metric descriptors are created for each monitored resource: usage, limit and utilization. You can follow each of these and create alerting policies if a threshold is reached.
+More complex scenarios are possible by leveraging and combining the 50 different timeseries created by this tool, and connecting them to Cloud Operations dashboards and alerts.
-## Usage
+Refer to the [Cloud Function deployment instructions](./deploy-cloud-function/) for a high level overview and an end-to-end deployment example, and to the[discovery tool documentation](./src/) to try it as a standalone program or to package it in alternative ways.
-Clone this repository, then go through the following steps to create resources:
-- Create a terraform.tfvars file with the following content:
- ```tfvars
- organization_id = " | ✓ | |
-| [monitored_projects_list](variables.tf#L36) | ID of the projects to be monitored (where limits and quotas data will be pulled). | list(string) | ✓ | |
-| [organization_id](variables.tf#L46) | The organization id for the associated services. | | ✓ | |
-| [prefix](variables.tf#L50) | Prefix used for resource names. | string | ✓ | |
-| [cf_version](variables.tf#L21) | Cloud Function version 2nd Gen or 1st Gen. Possible options: 'V1' or 'V2'.Use CFv2 if your Cloud Function timeouts after 9 minutes. By default it is using CFv1. | | | V1 |
-| [monitored_folders_list](variables.tf#L30) | ID of the projects to be monitored (where limits and quotas data will be pulled). | list(string) | | [] |
-| [monitoring_project_id](variables.tf#L41) | Monitoring project where the dashboard will be created and the solution deployed; a project will be created if set to empty string. | | | |
-| [project_monitoring_services](variables.tf#L59) | Service APIs enabled in the monitoring project if it will be created. | | | […] |
-| [region](variables.tf#L81) | Region used to deploy the cloud functions and scheduler. | | | europe-west1 |
-| [schedule_cron](variables.tf#L86) | Cron format schedule to run the Cloud Function. Default is every 10 minutes. | | | */10 * * * * |
+- support PSA-peered Google VPCs (Cloud SQL, Memorystore, etc.)
+- dynamic routes for VPCs/peering groups with "global routing" turned off
+- static routes calculation for projects/peering groups with custom routes import/export turned off
+- cross-organization peering groups
-
+If you are interested in this and/or would like to contribute, please open an issue in this repository or send us a PR.
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/main.py b/blueprints/cloud-operations/network-dashboard/cloud-function/main.py
deleted file mode 100644
index 8e7640dd4..000000000
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/main.py
+++ /dev/null
@@ -1,242 +0,0 @@
-#
-# 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.
-# CFv2 define whether to use Cloud function 2nd generation or 1st generation
-
-import re
-from distutils.command.config import config
-import os
-import time
-from google.cloud import monitoring_v3, asset_v1
-from google.protobuf import field_mask_pb2
-from googleapiclient import discovery
-from metrics import ilb_fwrules, firewall_policies, instances, networks, metrics, limits, peerings, routes, subnets, vpc_firewalls, secondarys
-
-CF_VERSION = os.environ.get("CF_VERSION")
-
-
-def get_monitored_projects_list(config):
- '''
- Gets the projects to be monitored from the MONITORED_FOLDERS_LIST environment variable.
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- Returns:
- monitored_projects (List of strings): Full list of projects to be monitored
- '''
- monitored_projects = config["monitored_projects"]
- monitored_folders = os.environ.get("MONITORED_FOLDERS_LIST").split(",")
-
- # Handling empty monitored folders list
- if monitored_folders == ['']:
- monitored_folders = []
-
- # Gets all projects under each monitored folder (and even in sub folders)
- for folder in monitored_folders:
- read_mask = field_mask_pb2.FieldMask()
- read_mask.FromJsonString('name,versionedResources')
-
- response = config["clients"]["asset_client"].search_all_resources(
- request={
- "scope": f"folders/{folder}",
- "asset_types": ["cloudresourcemanager.googleapis.com/Project"],
- "read_mask": read_mask
- })
-
- for resource in response:
- for versioned in resource.versioned_resources:
- for field_name, field_value in versioned.resource.items():
- if field_name == "projectId":
- project_id = field_value
- # Avoid duplicate
- if project_id not in monitored_projects:
- monitored_projects.append(project_id)
-
- print("List of projects to be monitored:")
- print(monitored_projects)
-
- return monitored_projects
-
-
-def monitoring_interval():
- '''
- Creates the monitoring interval of 24 hours
- Returns:
- monitoring_v3.TimeInterval: Monitoring time interval of 24h
- '''
- now = time.time()
- seconds = int(now)
- nanos = int((now - seconds) * 10**9)
- return monitoring_v3.TimeInterval({
- "end_time": {
- "seconds": seconds,
- "nanos": nanos
- },
- "start_time": {
- "seconds": (seconds - 24 * 60 * 60),
- "nanos": nanos
- },
- })
-
-
-config = {
- # Organization ID containing the projects to be monitored
- "organization":
- os.environ.get("ORGANIZATION_ID"),
- # list of projects from which function will get quotas information
- "monitored_projects":
- os.environ.get("MONITORED_PROJECTS_LIST").split(","),
- "monitoring_project":
- os.environ.get('MONITORING_PROJECT_ID'),
- "monitoring_project_link":
- f"projects/{os.environ.get('MONITORING_PROJECT_ID')}",
- "monitoring_interval":
- monitoring_interval(),
- "limit_names": {
- "GCE_INSTANCES":
- "compute.googleapis.com/quota/instances_per_vpc_network/limit",
- "L4":
- "compute.googleapis.com/quota/internal_lb_forwarding_rules_per_vpc_network/limit",
- "L7":
- "compute.googleapis.com/quota/internal_managed_forwarding_rules_per_vpc_network/limit",
- "SUBNET_RANGES":
- "compute.googleapis.com/quota/subnet_ranges_per_vpc_network/limit"
- },
- "lb_scheme": {
- "L7": "INTERNAL_MANAGED",
- "L4": "INTERNAL"
- },
- "clients": {
- "discovery_client": discovery.build('compute', 'v1'),
- "asset_client": asset_v1.AssetServiceClient(),
- "monitoring_client": monitoring_v3.MetricServiceClient()
- },
- # Improve performance for Asset Inventory queries on large environments
- "page_size":
- 500,
- "series_buffer": [],
-}
-
-
-def main(event, context=None):
- '''
- Cloud Function Entry point, called by the scheduler.
- Parameters:
- event: Not used for now (Pubsub trigger)
- context: Not used for now (Pubsub trigger)
- Returns:
- 'Function executed successfully'
- '''
- # Handling empty monitored projects list
- if config["monitored_projects"] == ['']:
- config["monitored_projects"] = []
-
- # Gets projects and folders to be monitored
- config["monitored_projects"] = get_monitored_projects_list(config)
-
- # Keep the monitoring interval up2date during each run
- config["monitoring_interval"] = monitoring_interval()
-
- metrics_dict, limits_dict = metrics.create_metrics(
- config["monitoring_project_link"], config)
- project_quotas_dict = limits.get_quota_project_limit(config)
-
- firewalls_dict = vpc_firewalls.get_firewalls_dict(config)
- firewall_policies_dict = firewall_policies.get_firewall_policies_dict(config)
-
- # IP utilization subnet level metrics
- subnets.get_subnets(config, metrics_dict)
-
- # IP utilization secondary range metrics
- secondarys.get_secondaries(config, metrics_dict)
-
- # Asset inventory queries
- gce_instance_dict = instances.get_gce_instance_dict(config)
- l4_forwarding_rules_dict = ilb_fwrules.get_forwarding_rules_dict(config, "L4")
- l7_forwarding_rules_dict = ilb_fwrules.get_forwarding_rules_dict(config, "L7")
- subnet_range_dict = networks.get_subnet_ranges_dict(config)
- static_routes_dict = routes.get_static_routes_dict(config)
- dynamic_routes_dict = routes.get_dynamic_routes(
- config, metrics_dict, limits_dict['dynamic_routes_per_network_limit'])
-
- try:
-
- # Per Project metrics
- vpc_firewalls.get_firewalls_data(config, metrics_dict, project_quotas_dict,
- firewalls_dict)
- # Per Firewall Policy metrics
- firewall_policies.get_firewal_policies_data(config, metrics_dict,
- firewall_policies_dict)
- # Per Network metrics
- instances.get_gce_instances_data(config, metrics_dict, gce_instance_dict,
- limits_dict['number_of_instances_limit'])
- ilb_fwrules.get_forwarding_rules_data(
- config, metrics_dict, l4_forwarding_rules_dict,
- limits_dict['internal_forwarding_rules_l4_limit'], "L4")
- ilb_fwrules.get_forwarding_rules_data(
- config, metrics_dict, l7_forwarding_rules_dict,
- limits_dict['internal_forwarding_rules_l7_limit'], "L7")
-
- routes.get_static_routes_data(config, metrics_dict, static_routes_dict,
- project_quotas_dict)
-
- peerings.get_vpc_peering_data(config, metrics_dict,
- limits_dict['number_of_vpc_peerings_limit'])
-
- # Per VPC peering group metrics
- metrics.get_pgg_data(
- config,
- metrics_dict["metrics_per_peering_group"]["instance_per_peering_group"],
- gce_instance_dict, config["limit_names"]["GCE_INSTANCES"],
- limits_dict['number_of_instances_ppg_limit'])
- metrics.get_pgg_data(
- config, metrics_dict["metrics_per_peering_group"]
- ["l4_forwarding_rules_per_peering_group"], l4_forwarding_rules_dict,
- config["limit_names"]["L4"],
- limits_dict['internal_forwarding_rules_l4_ppg_limit'])
- metrics.get_pgg_data(
- config, metrics_dict["metrics_per_peering_group"]
- ["l7_forwarding_rules_per_peering_group"], l7_forwarding_rules_dict,
- config["limit_names"]["L7"],
- limits_dict['internal_forwarding_rules_l7_ppg_limit'])
- metrics.get_pgg_data(
- config, metrics_dict["metrics_per_peering_group"]
- ["subnet_ranges_per_peering_group"], subnet_range_dict,
- config["limit_names"]["SUBNET_RANGES"],
- limits_dict['number_of_subnet_IP_ranges_ppg_limit'])
- #static
- routes.get_routes_ppg(
- config, metrics_dict["metrics_per_peering_group"]
- ["static_routes_per_peering_group"], static_routes_dict,
- limits_dict['static_routes_per_peering_group_limit'])
- #dynamic
- routes.get_routes_ppg(
- config, metrics_dict["metrics_per_peering_group"]
- ["dynamic_routes_per_peering_group"], dynamic_routes_dict,
- limits_dict['dynamic_routes_per_peering_group_limit'])
- except Exception as e:
- print("Error writing metrics")
- print(e)
- finally:
- metrics.flush_series_buffer(config)
-
- return 'Function execution completed'
-
-
-if CF_VERSION == "V2":
- import functions_framework
- main_http = functions_framework.http(main)
-
-if __name__ == "__main__":
- main(None, None)
\ No newline at end of file
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics.yaml b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics.yaml
deleted file mode 100644
index 217599634..000000000
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics.yaml
+++ /dev/null
@@ -1,223 +0,0 @@
-#
-# 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.
-#
----
-metrics_per_subnet:
- ip_usage_per_subnet:
- usage:
- name: number_of_ip_used
- description: Number of used IP addresses in the subnet.
- utilization:
- name: ip_addresses_per_subnet_utilization
- description: Percentage of IP used in the subnet.
- limit:
- name: number_of_max_ip
- description: Number of available IP addresses in the subnet.
- ip_usage_per_secondaryRange:
- usage:
- name: number_of_sr_ip_used
- description: Number of used IP addresses in the secondary range.
- utilization:
- name: ip_addresses_per_sr_utilization
- description: Percentage of IP used in the secondary range.
- limit:
- name: number_of_max_sr_ip
- description: Number of available IP addresses in the secondary range.
-metrics_per_network:
- instance_per_network:
- usage:
- name: number_of_instances_usage
- description: Number of instances per VPC network - usage.
- limit:
- name: number_of_instances_limit
- description: Number of instances per VPC network - limit.
- values:
- default_value: 15000
- utilization:
- name: number_of_instances_utilization
- description: Number of instances per VPC network - utilization.
- vpc_peering_active_per_network:
- usage:
- name: number_of_active_vpc_peerings_usage
- description: Number of active VPC Peerings per VPC - usage.
- limit:
- name: number_of_active_vpc_peerings_limit
- description: Number of active VPC Peerings per VPC - limit.
- values:
- default_value: 25
- utilization:
- name: number_of_active_vpc_peerings_utilization
- description: Number of active VPC Peerings per VPC - utilization.
- vpc_peering_per_network:
- usage:
- name: number_of_vpc_peerings_usage
- description: Number of VPC Peerings per VPC - usage.
- limit:
- name: number_of_vpc_peerings_limit
- description: Number of VPC Peerings per VPC - limit.
- values:
- default_value: 25
- https://www.googleapis.com/compute/v1/projects/net-dash-test-host-prod/global/networks/vpc-prod: 40
- utilization:
- name: number_of_vpc_peerings_utilization
- description: Number of VPC Peerings per VPC - utilization.
- l4_forwarding_rules_per_network:
- usage:
- name: internal_forwarding_rules_l4_usage
- description: Number of Internal Forwarding Rules for Internal L4 Load Balancers - usage.
- limit:
- name: internal_forwarding_rules_l4_limit
- description: Number of Internal Forwarding Rules for Internal L4 Load Balancers - limit.
- values:
- default_value: 500
- utilization:
- name: internal_forwarding_rules_l4_utilization
- description: Number of Internal Forwarding Rules for Internal L4 Load Balancers - utilization.
- l7_forwarding_rules_per_network:
- usage:
- name: internal_forwarding_rules_l7_usage
- description: Number of Internal Forwarding Rules for Internal L7 Load Balancers per network - usage.
- limit:
- name: internal_forwarding_rules_l7_limit
- description: Number of Internal Forwarding Rules for Internal L7 Load Balancers per network - effective limit.
- values:
- default_value: 75
- utilization:
- name: internal_forwarding_rules_l7_utilization
- description: Number of Internal Forwarding Rules for Internal L7 Load Balancers per Vnetwork - utilization.
- dynamic_routes_per_network:
- usage:
- name: dynamic_routes_per_network_usage
- description: Number of Dynamic routes per network - usage.
- limit:
- name: dynamic_routes_per_network_limit
- description: Number of Dynamic routes per network - limit.
- values:
- default_value: 100
- utilization:
- name: dynamic_routes_per_network_utilization
- description: Number of Dynamic routes per network - utilization.
- #static routes limit is per project, but usage is per network
- static_routes_per_project:
- usage:
- name: static_routes_per_project_vpc_usage
- description: Number of Static routes per project and network - usage.
- limit:
- name: static_routes_per_project_limit
- description: Number of Static routes per project - limit.
- values:
- default_value: 250
- utilization:
- name: static_routes_per_project_utilization
- description: Number of Static routes per project - utilization.
-metrics_per_peering_group:
- l4_forwarding_rules_per_peering_group:
- usage:
- name: internal_forwarding_rules_l4_ppg_usage
- description: Number of Internal Forwarding Rules for Internal L4 Load Balancers per VPC peering group - usage.
- limit:
- name: internal_forwarding_rules_l4_ppg_limit
- description: Number of Internal Forwarding Rules for Internal L4 Load Balancers per VPC peering group - effective limit.
- values:
- default_value: 500
- utilization:
- name: internal_forwarding_rules_l4_ppg_utilization
- description: Number of Internal Forwarding Rules for Internal L4 Load Balancers per VPC peering group - utilization.
- l7_forwarding_rules_per_peering_group:
- usage:
- name: internal_forwarding_rules_l7_ppg_usage
- description: Number of Internal Forwarding Rules for Internal L7 Load Balancers per VPC peering group - usage.
- limit:
- name: internal_forwarding_rules_l7_ppg_limit
- description: Number of Internal Forwarding Rules for Internal L7 Load Balancers per VPC peering group - effective limit.
- values:
- default_value: 175
- utilization:
- name: internal_forwarding_rules_l7_ppg_utilization
- description: Number of Internal Forwarding Rules for Internal L7 Load Balancers per VPC peering group - utilization.
- subnet_ranges_per_peering_group:
- usage:
- name: number_of_subnet_IP_ranges_ppg_usage
- description: Number of Subnet Ranges per peering group - usage.
- limit:
- name: number_of_subnet_IP_ranges_ppg_limit
- description: Number of Subnet Ranges per peering group - effective limit.
- values:
- default_value: 400
- utilization:
- name: number_of_subnet_IP_ranges_ppg_utilization
- description: Number of Subnet Ranges per peering group - utilization.
- instance_per_peering_group:
- usage:
- name: number_of_instances_ppg_usage
- description: Number of instances per peering group - usage.
- limit:
- name: number_of_instances_ppg_limit
- description: Number of instances per peering group - limit.
- values:
- default_value: 15500
- utilization:
- name: number_of_instances_ppg_utilization
- description: Number of instances per peering group - utilization.
- dynamic_routes_per_peering_group:
- usage:
- name: dynamic_routes_per_peering_group_usage
- description: Number of Dynamic routes per peering group - usage.
- limit:
- name: dynamic_routes_per_peering_group_limit
- description: Number of Dynamic routes per peering group - limit.
- values:
- default_value: 300
- utilization:
- name: dynamic_routes_per_peering_group_utilization
- description: Number of Dynamic routes per peering group - utilization.
- static_routes_per_peering_group:
- usage:
- name: static_routes_per_peering_group_usage
- description: Number of Static routes per peering group - usage.
- limit:
- name: static_routes_per_peering_group_limit
- description: Number of Static routes per peering group - limit.
- values:
- default_value: 300
- utilization:
- name: static_routes_per_peering_group_utilization
- description: Number of Static routes per peering group - utilization.
-metrics_per_project:
- firewalls:
- usage:
- name: firewalls_per_project_vpc_usage
- description: Number of VPC firewall rules in a project - usage.
- limit:
- # Firewalls limit is per project and we get the limit for the GCP quota API in vpc_firewalls.py
- name: firewalls_per_project_limit
- description: Number of VPC firewall rules in a project - limit.
- utilization:
- name: firewalls_per_project_utilization
- description: Number of VPC firewall rules in a project - utilization.
-metrics_per_firewall_policy:
- firewall_policy_tuples:
- usage:
- name: firewall_policy_tuples_per_policy_usage
- description: Number of tuples in a firewall policy - usage.
- limit:
- # This limit is not visibile through Google APIs, set default_value
- name: firewall_policy_tuples_per_policy_limit
- description: Number of tuples in a firewall policy - limit.
- values:
- default_value: 2000
- utilization:
- name: firewall_policy_tuples_per_policy_utilization
- description: Number of tuples in a firewall policy - utilization.
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/firewall_policies.py b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/firewall_policies.py
deleted file mode 100644
index 95a26db38..000000000
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/firewall_policies.py
+++ /dev/null
@@ -1,118 +0,0 @@
-#
-# 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.
-#
-
-import re
-import time
-
-from collections import defaultdict
-from pydoc import doc
-from collections import defaultdict
-from google.protobuf import field_mask_pb2
-from . import metrics, networks, limits
-
-
-def get_firewall_policies_dict(config: dict):
- '''
- Calls the Asset Inventory API to get all Firewall Policies under the GCP organization, including children
- Ignores monitored projects list: returns all policies regardless of their parent resource
- Parameters:
- config (dict): The dict containing config like clients and limits
- Returns:
- firewal_policies_dict (dictionary of dictionary): Keys are policy ids, subkeys are policy field values
- '''
-
- firewall_policies_dict = defaultdict(int)
- read_mask = field_mask_pb2.FieldMask()
- read_mask.FromJsonString('name,versionedResources')
-
- response = config["clients"]["asset_client"].search_all_resources(
- request={
- "scope": f"organizations/{config['organization']}",
- "asset_types": ["compute.googleapis.com/FirewallPolicy"],
- "read_mask": read_mask,
- })
- for resource in response:
- for versioned in resource.versioned_resources:
- firewall_policy = dict()
- for field_name, field_value in versioned.resource.items():
- firewall_policy[field_name] = field_value
- firewall_policies_dict[firewall_policy['id']] = firewall_policy
- return firewall_policies_dict
-
-
-def get_firewal_policies_data(config, metrics_dict, firewall_policies_dict):
- '''
- Gets the data for VPC Firewall Policies in an organization, including children. All folders are considered,
- only projects in the monitored projects list are considered.
- Parameters:
- config (dict): The dict containing config like clients and limits
- metrics_dict (dictionary of dictionary of string: string): metrics names and descriptions.
- firewall_policies_dict (dictionary of of dictionary of string: string): Keys are policies ids, subkeys are policies values
- Returns:
- None
- '''
-
- current_tuples_limit = None
- try:
- current_tuples_limit = metrics_dict["metrics_per_firewall_policy"][
- "firewall_policy_tuples"]["limit"]["values"]["default_value"]
- except Exception:
- print(
- f"Could not determine number of tuples metric limit due to missing default value"
- )
- if current_tuples_limit < 0:
- print(
- f"Could not determine number of tuples metric limit as default value is <= 0"
- )
-
- timestamp = time.time()
- for firewall_policy_key in firewall_policies_dict:
- firewall_policy = firewall_policies_dict[firewall_policy_key]
-
- # may either be a org, a folder, or a project
- # folder and org require to split {folder,organization}\/\w+
- parent = re.search("(\w+$)", firewall_policy["parent"]).group(
- 1) if "parent" in firewall_policy else re.search(
- "([\d,a-z,-]+)(\/[\d,a-z,-]+\/firewallPolicies/[\d,a-z,-]*$)",
- firewall_policy["selfLink"]).group(1)
- parent_type = re.search("(^\w+)", firewall_policy["parent"]).group(
- 1) if "parent" in firewall_policy else "projects"
-
- if parent_type == "projects" and parent not in config["monitored_projects"]:
- continue
-
- metric_labels = {'parent': parent, 'parent_type': parent_type}
-
- metric_labels["name"] = firewall_policy[
- "displayName"] if "displayName" in firewall_policy else firewall_policy[
- "name"]
-
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_firewall_policy"]
- [f"firewall_policy_tuples"]["usage"]["name"],
- firewall_policy['ruleTupleCount'], metric_labels, timestamp=timestamp)
- if not current_tuples_limit == None and current_tuples_limit > 0:
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_firewall_policy"]
- [f"firewall_policy_tuples"]["limit"]["name"], current_tuples_limit,
- metric_labels, timestamp=timestamp)
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_firewall_policy"]
- [f"firewall_policy_tuples"]["utilization"]["name"],
- firewall_policy['ruleTupleCount'] / current_tuples_limit,
- metric_labels, timestamp=timestamp)
-
- print(f"Buffered number tuples per Firewall Policy")
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/ilb_fwrules.py b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/ilb_fwrules.py
deleted file mode 100644
index de8274d97..000000000
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/ilb_fwrules.py
+++ /dev/null
@@ -1,122 +0,0 @@
-#
-# 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.
-#
-
-import time
-
-from collections import defaultdict
-from google.protobuf import field_mask_pb2
-from . import metrics, networks, limits
-
-
-def get_forwarding_rules_dict(config, layer: str):
- '''
- Calls the Asset Inventory API to get all L4 Forwarding Rules under the GCP organization.
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- layer (string): the Layer to get Forwarding rules (L4/L7)
- Returns:
- forwarding_rules_dict (dictionary of string: int): Keys are the network links and values are the number of Forwarding Rules per network.
- '''
-
- read_mask = field_mask_pb2.FieldMask()
- read_mask.FromJsonString('name,versionedResources')
-
- forwarding_rules_dict = defaultdict(int)
-
- response = config["clients"]["asset_client"].search_all_resources(
- request={
- "scope": f"organizations/{config['organization']}",
- "asset_types": ["compute.googleapis.com/ForwardingRule"],
- "read_mask": read_mask,
- "page_size": config["page_size"],
- })
-
- for resource in response:
- internal = False
- network_link = ""
- for versioned in resource.versioned_resources:
- for field_name, field_value in versioned.resource.items():
- if field_name == "loadBalancingScheme":
- internal = (field_value == config["lb_scheme"][layer])
- if field_name == "network":
- network_link = field_value
- if internal:
- if network_link in forwarding_rules_dict:
- forwarding_rules_dict[network_link] += 1
- else:
- forwarding_rules_dict[network_link] = 1
-
- return forwarding_rules_dict
-
-
-def get_forwarding_rules_data(config, metrics_dict, forwarding_rules_dict,
- limit_dict, layer):
- '''
- Gets the data for L4 Internal Forwarding Rules per VPC Network and writes it to the metric defined in forwarding_rules_metric.
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- metrics_dict (dictionary of dictionary of string: string): metrics names and descriptions.
- forwarding_rules_dict (dictionary of string: int): Keys are the network links and values are the number of Forwarding Rules per network.
- limit_dict (dictionary of string:int): Dictionary with the network link as key and the limit as value.
- layer (string): the Layer to get Forwarding rules (L4/L7)
- Returns:
- None
- '''
-
- timestamp = time.time()
- for project_id in config["monitored_projects"]:
- network_dict = networks.get_networks(config, project_id)
-
- current_quota_limit = limits.get_quota_current_limit(
- config, f"projects/{project_id}", config["limit_names"][layer])
-
- if current_quota_limit is None:
- print(
- f"Could not determine {layer} forwarding rules to metric for projects/{project_id} due to missing quotas"
- )
- continue
-
- current_quota_limit_view = metrics.customize_quota_view(current_quota_limit)
-
- for net in network_dict:
- limits.set_limits(net, current_quota_limit_view, limit_dict)
-
- usage = 0
- if net['self_link'] in forwarding_rules_dict:
- usage = forwarding_rules_dict[net['self_link']]
-
- metric_labels = {
- 'project': project_id,
- 'network_name': net['network_name']
- }
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]
- [f"{layer.lower()}_forwarding_rules_per_network"]["usage"]["name"],
- usage, metric_labels, timestamp=timestamp)
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]
- [f"{layer.lower()}_forwarding_rules_per_network"]["limit"]["name"],
- net['limit'], metric_labels, timestamp=timestamp)
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]
- [f"{layer.lower()}_forwarding_rules_per_network"]["utilization"]
- ["name"], usage / net['limit'], metric_labels, timestamp=timestamp)
-
- print(
- f"Buffered number of {layer} forwarding rules to metric for projects/{project_id}"
- )
\ No newline at end of file
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/instances.py b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/instances.py
deleted file mode 100644
index d3b72e678..000000000
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/instances.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#
-# 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.
-#
-
-import time
-
-from code import interact
-from collections import defaultdict
-from . import metrics, networks, limits
-
-
-def get_gce_instance_dict(config: dict):
- '''
- Calls the Asset Inventory API to get all GCE instances under the GCP organization.
-
- Parameters:
- config (dict): The dict containing config like clients and limits
-
- Returns:
- gce_instance_dict (dictionary of string: int): Keys are the network links and values are the number of GCE Instances per network.
- '''
-
- gce_instance_dict = defaultdict(int)
-
- response = config["clients"]["asset_client"].search_all_resources(
- request={
- "scope": f"organizations/{config['organization']}",
- "asset_types": ["compute.googleapis.com/Instance"],
- "page_size": config["page_size"],
- })
- for resource in response:
- for field_name, field_value in resource.additional_attributes.items():
- if field_name == "networkInterfaceNetworks":
- for network in field_value:
- if network in gce_instance_dict:
- gce_instance_dict[network] += 1
- else:
- gce_instance_dict[network] = 1
-
- return gce_instance_dict
-
-
-def get_gce_instances_data(config, metrics_dict, gce_instance_dict, limit_dict):
- '''
- Gets the data for GCE instances per VPC Network and writes it to the metric defined in instance_metric.
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- metrics_dict (dictionary of dictionary of string: string): metrics names and descriptions
- gce_instance_dict (dictionary of string: int): Keys are the network links and values are the number of GCE Instances per network.
- limit_dict (dictionary of string:int): Dictionary with the network link as key and the limit as value
- Returns:
- gce_instance_dict
- '''
- timestamp = time.time()
- for project_id in config["monitored_projects"]:
- network_dict = networks.get_networks(config, project_id)
-
- current_quota_limit = limits.get_quota_current_limit(
- config, f"projects/{project_id}",
- config["limit_names"]["GCE_INSTANCES"])
- if current_quota_limit is None:
- print(
- f"Could not determine number of instances for projects/{project_id} due to missing quotas"
- )
-
- current_quota_limit_view = metrics.customize_quota_view(current_quota_limit)
-
- for net in network_dict:
- limits.set_limits(net, current_quota_limit_view, limit_dict)
-
- usage = 0
- if net['self_link'] in gce_instance_dict:
- usage = gce_instance_dict[net['self_link']]
-
- metric_labels = {
- 'project': project_id,
- 'network_name': net['network_name']
- }
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]["instance_per_network"]
- ["usage"]["name"], usage, metric_labels, timestamp=timestamp)
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]["instance_per_network"]
- ["limit"]["name"], net['limit'], metric_labels, timestamp=timestamp)
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]["instance_per_network"]
- ["utilization"]["name"], usage / net['limit'], metric_labels,
- timestamp=timestamp)
-
- print(f"Buffered number of instances to metric for projects/{project_id}")
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/limits.py b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/limits.py
deleted file mode 100644
index edd4a50b3..000000000
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/limits.py
+++ /dev/null
@@ -1,236 +0,0 @@
-#
-# 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.
-#
-
-import time
-
-from google.api_core import exceptions
-from google.cloud import monitoring_v3
-from . import metrics
-
-
-def get_quotas_dict(quotas_list):
- '''
- Creates a dictionary of quotas from a list, with lower case quota name as keys
- Parameters:
- quotas_array (array): array of quotas
- Returns:
- quotas_dict (dict): dictionary of quotas
- '''
- quota_keys = [q['metric'] for q in quotas_list]
- quotas_dict = dict()
- i = 0
- for key in quota_keys:
- if ("metric" in quotas_list[i]):
- del (quotas_list[i]["metric"])
- quotas_dict[key.lower()] = quotas_list[i]
- i += 1
- return quotas_dict
-
-
-def get_quota_project_limit(config, regions=["global"]):
- '''
- Retrieves quotas for all monitored project in selected regions, default 'global'
- Parameters:
- project_link (string): Project link.
- Returns:
- quotas (dict): quotas for all selected regions, default 'global'
- '''
- try:
- request = {}
- quotas = dict()
- for project in config["monitored_projects"]:
- quotas[project] = dict()
- if regions != ["global"]:
- for region in regions:
- request = config["clients"]["discovery_client"].compute.regions().get(
- region=region, project=project)
- response = request.execute()
- quotas[project][region] = get_quotas_dict(response['quotas'])
- else:
- region = "global"
- request = config["clients"]["discovery_client"].projects().get(
- project=project, fields="quotas")
- response = request.execute()
- quotas[project][region] = get_quotas_dict(response['quotas'])
-
- return quotas
- except exceptions.PermissionDenied as err:
- print(
- f"Warning: error reading quotas for {project}. " +
- f"This can happen if you don't have permissions on the project, for example if the project is in another organization or a Google managed project"
- )
- return None
-
-
-def get_ppg(network_link, limit_dict):
- '''
- Checks if this network has a specific limit for a metric, if so, returns that limit, if not, returns the default limit.
-
- Parameters:
- network_link (string): VPC network link.
- limit_list (list of string): Used to get the limit per VPC or the default limit.
- Returns:
- limit_dict (dictionary of string:int): Dictionary with the network link as key and the limit as value
- '''
- if network_link in limit_dict:
- return limit_dict[network_link]
- else:
- if 'default_value' in limit_dict:
- return limit_dict['default_value']
- else:
- print(f"Error: limit not found for {network_link}")
- return 0
-
-
-def set_limits(network_dict, quota_limit, limit_dict):
- '''
- Updates the network dictionary with quota limit values.
-
- Parameters:
- network_dict (dictionary of string: string): Contains network information.
- quota_limit (list of dictionaries of string: string): Current quota limit.
- limit_dict (dictionary of string:int): Dictionary with the network link as key and the limit as value
- Returns:
- None
- '''
-
- network_dict['limit'] = None
-
- if quota_limit:
- for net in quota_limit:
- if net['network_id'] == network_dict['network_id']:
- network_dict['limit'] = net['value']
- return
-
- network_link = f"https://www.googleapis.com/compute/v1/projects/{network_dict['project_id']}/global/networks/{network_dict['network_name']}"
-
- if network_link in limit_dict:
- network_dict['limit'] = limit_dict[network_link]
- else:
- if 'default_value' in limit_dict:
- network_dict['limit'] = limit_dict['default_value']
- else:
- print(f"Error: Couldn't find limit for {network_link}")
- network_dict['limit'] = 0
-
-
-def get_quota_current_limit(config, project_link, metric_name):
- '''
- Retrieves limit for a specific metric.
-
- Parameters:
- project_link (string): Project link.
- metric_name (string): Name of the metric.
- Returns:
- results_list (list of string): Current limit.
- '''
-
- try:
- results = config["clients"]["monitoring_client"].list_time_series(
- request={
- "name": project_link,
- "filter": f'metric.type = "{metric_name}"',
- "interval": config["monitoring_interval"],
- "view": monitoring_v3.ListTimeSeriesRequest.TimeSeriesView.FULL
- })
- results_list = list(results)
- return results_list
- except exceptions.PermissionDenied as err:
- print(
- f"Warning: error reading quotas for {project_link}. " +
- f"This can happen if you don't have permissions on the project, for example if the project is in another organization or a Google managed project"
- )
- return None
-
-
-def count_effective_limit(config, project_id, network_dict, usage_metric_name,
- limit_metric_name, utilization_metric_name,
- limit_dict, timestamp=None):
- '''
- Calculates the effective limits (using algorithm in the link below) for peering groups and writes data (usage, limit, utilization) to the custom metrics.
- Source: https://cloud.google.com/vpc/docs/quota#vpc-peering-effective-limit
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- project_id (string): Project ID for the project to be analyzed.
- network_dict (dictionary of string: string): Contains all required information about the network to get the usage, limit and utilization.
- usage_metric_name (string): Name of the custom metric to be populated for usage per VPC peering group.
- limit_metric_name (string): Name of the custom metric to be populated for limit per VPC peering group.
- utilization_metric_name (string): Name of the custom metric to be populated for utilization per VPC peering group.
- limit_dict (dictionary of string:int): Dictionary containing the limit per peering group (either VPC specific or default limit).
- timestamp (time): timestamp to be recorded for all points
- Returns:
- None
- '''
-
- if timestamp == None:
- timestamp = time.time()
-
- if network_dict['peerings'] == []:
- return
-
- # Get usage: Sums usage for current network + all peered networks
- peering_group_usage = network_dict['usage']
- for peered_network in network_dict['peerings']:
- if 'usage' not in peered_network:
- print(
- f"Cannot add metrics for peered network in projects/{project_id} as no usage metrics exist due to missing permissions"
- )
- continue
- peering_group_usage += peered_network['usage']
-
- network_link = f"https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network_dict['network_name']}"
-
- # Calculates effective limit: Step 1: max(per network limit, per network_peering_group limit)
- limit_step1 = max(network_dict['limit'], get_ppg(network_link, limit_dict))
-
- # Calculates effective limit: Step 2: List of max(per network limit, per network_peering_group limit) for each peered network
- limit_step2 = []
- for peered_network in network_dict['peerings']:
- peered_network_link = f"https://www.googleapis.com/compute/v1/projects/{peered_network['project_id']}/global/networks/{peered_network['network_name']}"
-
- if 'limit' in peered_network:
- limit_step2.append(
- max(peered_network['limit'], get_ppg(peered_network_link,
- limit_dict)))
- else:
- print(
- f"Ignoring projects/{peered_network['project_id']} for limits in peering group of project {project_id} as no limits are available."
- +
- "This can happen if you don't have permissions on the project, for example if the project is in another organization or a Google managed project"
- )
-
- # Calculates effective limit: Step 3: Find minimum from the list created by Step 2
- limit_step3 = 0
- if len(limit_step2) > 0:
- limit_step3 = min(limit_step2)
-
- # Calculates effective limit: Step 4: Find maximum from step 1 and step 3
- effective_limit = max(limit_step1, limit_step3)
- utilization = peering_group_usage / effective_limit
- metric_labels = {
- 'project': project_id,
- 'network_name': network_dict['network_name']
- }
- metrics.append_data_to_series_buffer(config, usage_metric_name,
- peering_group_usage, metric_labels,
- timestamp=timestamp)
- metrics.append_data_to_series_buffer(config, limit_metric_name,
- effective_limit, metric_labels,
- timestamp=timestamp)
- metrics.append_data_to_series_buffer(config, utilization_metric_name,
- utilization, metric_labels,
- timestamp=timestamp)
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/metrics.py b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/metrics.py
deleted file mode 100644
index 8e0c4082b..000000000
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/metrics.py
+++ /dev/null
@@ -1,267 +0,0 @@
-#
-# 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.
-#
-
-from curses import KEY_MARK
-import re
-import time
-import yaml
-from google.api import metric_pb2 as ga_metric
-from google.cloud import monitoring_v3
-from . import peerings, limits, networks
-
-
-def create_metrics(monitoring_project, config):
- '''
- Creates all Cloud Monitoring custom metrics based on the metric.yaml file
- Parameters:
- monitoring_project (string): the project where the metrics are written to
- config (dict): The dict containing config like clients and limits
- Returns:
- metrics_dict (dictionary of dictionary of string: string): metrics names and descriptions
- limits_dict (dictionary of dictionary of string: int): limits_dict[metric_name]: dict[network_name] = limit_value
- '''
- client = config["clients"]["monitoring_client"]
- existing_metrics = []
- for desc in client.list_metric_descriptors(name=monitoring_project):
- existing_metrics.append(desc.type)
- limits_dict = {}
-
- with open("./metrics.yaml", 'r') as stream:
- try:
- metrics_dict = yaml.safe_load(stream)
-
- for metric_list in metrics_dict.values():
- for metric_name, metric in metric_list.items():
- for sub_metric_key, sub_metric in metric.items():
- metric_link = f"custom.googleapis.com/{sub_metric['name']}"
- # If the metric doesn't exist yet, then we create it
- if metric_link not in existing_metrics:
- create_metric(sub_metric["name"], sub_metric["description"],
- monitoring_project, config)
- # Parse limits for network and peering group metrics
- # Subnet level metrics have a different limit: the subnet IP range size
- if sub_metric_key == "limit" and (
- metric_name != "ip_usage_per_subnet" and
- metric_name != "ip_usage_per_secondaryRange"):
- limits_dict_for_metric = {}
- if "values" in sub_metric:
- for network_link, limit_value in sub_metric["values"].items():
- limits_dict_for_metric[network_link] = limit_value
- limits_dict[sub_metric["name"]] = limits_dict_for_metric
-
- return metrics_dict, limits_dict
- except yaml.YAMLError as exc:
- print(exc)
-
-
-def create_metric(metric_name, description, monitoring_project, config):
- '''
- Creates a Cloud Monitoring metric based on the parameter given if the metric is not already existing
- Parameters:
- metric_name (string): Name of the metric to be created
- description (string): Description of the metric to be created
- monitoring_project (string): the project where the metrics are written to
- config (dict): The dict containing config like clients and limits
- Returns:
- None
- '''
- client = config["clients"]["monitoring_client"]
-
- descriptor = ga_metric.MetricDescriptor()
- descriptor.type = f"custom.googleapis.com/{metric_name}"
- descriptor.metric_kind = ga_metric.MetricDescriptor.MetricKind.GAUGE
- descriptor.value_type = ga_metric.MetricDescriptor.ValueType.DOUBLE
- descriptor.description = description
- descriptor = client.create_metric_descriptor(name=monitoring_project,
- metric_descriptor=descriptor)
- print("Created {}.".format(descriptor.name))
-
-
-def append_data_to_series_buffer(config, metric_name, metric_value,
- metric_labels, timestamp=None):
- '''
- Appends data to Cloud Monitoring custom metrics, using a buffer. buffer is flushed every BUFFER_LEN elements,
- any unflushed series is discarded upon function closure
- Parameters:
- config (dict): The dict containing config like clients and limits
- metric_name (string): Name of the metric
- metric_value (int): Value for the data point of the metric.
- matric_labels (dictionary of dictionary of string: string): metric labels names and values
- timestamp (float): seconds since the epoch, in UTC
- Returns:
- usage (int): Current usage for that network.
- limit (int): Current usage for that network.
- '''
-
- # Configurable buffer size to improve performance when writing datapoints to metrics
- buffer_len = 10
-
- series = monitoring_v3.TimeSeries()
- series.metric.type = f"custom.googleapis.com/{metric_name}"
- series.resource.type = "global"
-
- for label_name in metric_labels:
- if (metric_labels[label_name] != None):
- series.metric.labels[label_name] = metric_labels[label_name]
-
- timestamp = timestamp if timestamp != None else time.time()
- seconds = int(timestamp)
- nanos = int((timestamp - seconds) * 10**9)
- interval = monitoring_v3.TimeInterval(
- {"end_time": {
- "seconds": seconds,
- "nanos": nanos
- }})
- point = monitoring_v3.Point({
- "interval": interval,
- "value": {
- "double_value": metric_value
- }
- })
- series.points = [point]
-
- # TODO: sometimes this cashes with 'DeadlineExceeded: 504 Deadline expired before operation could complete' error
- # Implement exponential backoff retries?
- config["series_buffer"].append(series)
- if len(config["series_buffer"]) >= buffer_len:
- flush_series_buffer(config)
-
-
-def flush_series_buffer(config):
- '''
- writes buffered metrics to Google Cloud Monitoring, empties buffer upon both failure/success
- config (dict): The dict containing config like clients and limits
- '''
- try:
- if config["series_buffer"] and len(config["series_buffer"]) > 0:
- client = config["clients"]["monitoring_client"]
- client.create_time_series(name=config["monitoring_project_link"],
- time_series=config["series_buffer"])
- series_names = [
- re.search("\/(.+$)", series.metric.type).group(1)
- for series in config["series_buffer"]
- ]
- print("Wrote time series: ", series_names)
- except Exception as e:
- print("Error while flushing series buffer")
- print(e)
-
- config["series_buffer"] = []
-
-
-def get_pgg_data(config, metric_dict, usage_dict, limit_metric, limit_dict):
- '''
- This function gets the usage, limit and utilization per VPC peering group for a specific metric for all projects to be monitored.
- Parameters:
- config (dict): The dict containing config like clients and limits
- metric_dict (dictionary of string: string): Dictionary with the metric names and description, that will be used to populate the metrics
- usage_dict (dictionnary of string:int): Dictionary with the network link as key and the number of resources as value
- limit_metric (string): Name of the existing GCP metric for limit per VPC network
- limit_dict (dictionary of string:int): Dictionary with the network link as key and the limit as value
- Returns:
- None
- '''
- for project_id in config["monitored_projects"]:
- network_dict_list = peerings.gather_peering_data(config, project_id)
- # Network dict list is a list of dictionary (one for each network)
- # For each network, this dictionary contains:
- # project_id, network_name, network_id, usage, limit, peerings (list of peered networks)
- # peerings is a list of dictionary (one for each peered network) and contains:
- # project_id, network_name, network_id
- current_quota_limit = limits.get_quota_current_limit(
- config, f"projects/{project_id}", limit_metric)
- if current_quota_limit is None:
- print(
- f"Could not determine number of L7 forwarding rules to metric for projects/{project_id} due to missing quotas"
- )
- continue
-
- current_quota_limit_view = customize_quota_view(current_quota_limit)
-
- timestamp = time.time()
- # For each network in this GCP project
- for network_dict in network_dict_list:
- if network_dict['network_id'] == 0:
- print(
- f"Could not determine {metric_dict['usage']['name']} for peering group {network_dict['network_name']} in {project_id} due to missing permissions."
- )
- continue
- network_link = f"https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network_dict['network_name']}"
-
- limit = networks.get_limit_network(network_dict, network_link,
- current_quota_limit_view, limit_dict)
-
- usage = 0
- if network_link in usage_dict:
- usage = usage_dict[network_link]
-
- # Here we add usage and limit to the network dictionary
- network_dict["usage"] = usage
- network_dict["limit"] = limit
-
- # For every peered network, get usage and limits
- for peered_network_dict in network_dict['peerings']:
- peered_network_link = f"https://www.googleapis.com/compute/v1/projects/{peered_network_dict['project_id']}/global/networks/{peered_network_dict['network_name']}"
- peered_usage = 0
- if peered_network_link in usage_dict:
- peered_usage = usage_dict[peered_network_link]
-
- current_peered_quota_limit = limits.get_quota_current_limit(
- config, f"projects/{peered_network_dict['project_id']}",
- limit_metric)
- if current_peered_quota_limit is None:
- print(
- f"Could not determine metrics for peering to projects/{peered_network_dict['project_id']} due to missing quotas"
- )
- continue
-
- peering_project_limit = customize_quota_view(current_peered_quota_limit)
-
- peered_limit = networks.get_limit_network(peered_network_dict,
- peered_network_link,
- peering_project_limit,
- limit_dict)
- # Here we add usage and limit to the peered network dictionary
- peered_network_dict["usage"] = peered_usage
- peered_network_dict["limit"] = peered_limit
-
- limits.count_effective_limit(config, project_id, network_dict,
- metric_dict["usage"]["name"],
- metric_dict["limit"]["name"],
- metric_dict["utilization"]["name"],
- limit_dict, timestamp)
- print(
- f"Buffered {metric_dict['usage']['name']} for peering group {network_dict['network_name']} in {project_id}"
- )
-
-
-def customize_quota_view(quota_results):
- '''
- Customize the quota output for an easier parsable output.
- Parameters:
- quota_results (string): Input from get_quota_current_usage or get_quota_current_limit. Contains the Current usage or limit for all networks in that project.
- Returns:
- quotaViewList (list of dictionaries of string: string): Current quota usage or limit.
- '''
- quotaViewList = []
- for result in quota_results:
- quotaViewJson = {}
- quotaViewJson.update(dict(result.resource.labels))
- quotaViewJson.update(dict(result.metric.labels))
- for val in result.points:
- quotaViewJson.update({'value': val.value.int64_value})
- quotaViewList.append(quotaViewJson)
- return quotaViewList
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/networks.py b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/networks.py
deleted file mode 100644
index 094f374ed..000000000
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/networks.py
+++ /dev/null
@@ -1,160 +0,0 @@
-#
-# 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.
-#
-
-from code import interact
-from collections import defaultdict
-from google.protobuf import field_mask_pb2
-from googleapiclient import errors
-import http
-
-
-def get_subnet_ranges_dict(config: dict):
- '''
- Calls the Asset Inventory API to get all Subnet ranges under the GCP organization.
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- Returns:
- subnet_range_dict (dictionary of string: int): Keys are the network links and values are the number of subnet ranges per network.
- '''
-
- subnet_range_dict = defaultdict(int)
- read_mask = field_mask_pb2.FieldMask()
- read_mask.FromJsonString('name,versionedResources')
-
- response = config["clients"]["asset_client"].search_all_resources(
- request={
- "scope": f"organizations/{config['organization']}",
- "asset_types": ["compute.googleapis.com/Subnetwork"],
- "read_mask": read_mask,
- "page_size": config["page_size"],
- })
- for resource in response:
- ranges = 0
- network_link = None
-
- for versioned in resource.versioned_resources:
- for field_name, field_value in versioned.resource.items():
- if field_name == "network":
- network_link = field_value
- ranges += 1
- if field_name == "secondaryIpRanges":
- for range in field_value:
- ranges += 1
-
- if network_link in subnet_range_dict:
- subnet_range_dict[network_link] += ranges
- else:
- subnet_range_dict[network_link] = ranges
-
- return subnet_range_dict
-
-
-def get_networks(config, project_id):
- '''
- Returns a dictionary of all networks in a project.
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- project_id (string): Project ID for the project containing the networks.
- Returns:
- network_dict (dictionary of string: string): Contains the project_id, network_name(s) and network_id(s)
- '''
- request = config["clients"]["discovery_client"].networks().list(
- project=project_id)
- response = request.execute()
- network_dict = []
- if 'items' in response:
- for network in response['items']:
- network_name = network['name']
- network_id = network['id']
- self_link = network['selfLink']
- d = {
- 'project_id': project_id,
- 'network_name': network_name,
- 'network_id': network_id,
- 'self_link': self_link
- }
- network_dict.append(d)
- return network_dict
-
-
-def get_network_id(config, project_id, network_name):
- '''
- Returns the network_id for a specific project / network name.
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- project_id (string): Project ID for the project containing the networks.
- network_name (string): Name of the network
- Returns:
- network_id (int): Network ID.
- '''
- request = config["clients"]["discovery_client"].networks().list(
- project=project_id)
- try:
- response = request.execute()
- except errors.HttpError as err:
- # TODO: log proper warning
- if err.resp.status == http.HTTPStatus.FORBIDDEN:
- print(
- f"Warning: error reading networks for {project_id}. " +
- f"This can happen if you don't have permissions on the project, for example if the project is in another organization or a Google managed project"
- )
- else:
- print(f"Warning: error reading networks for {project_id}: {err}")
- return 0
-
- network_id = 0
-
- if 'items' in response:
- for network in response['items']:
- if network['name'] == network_name:
- network_id = network['id']
- break
-
- if network_id == 0:
- print(f"Error: network_id not found for {network_name} in {project_id}")
-
- return network_id
-
-
-def get_limit_network(network_dict, network_link, quota_limit, limit_dict):
- '''
- Returns limit for a specific network and metric, using the GCP quota metrics or the values in the yaml file if not found.
-
- Parameters:
- network_dict (dictionary of string: string): Contains network information.
- network_link (string): Contains network link
- quota_limit (list of dictionaries of string: string): Current quota limit for all networks in that project.
- limit_dict (dictionary of string:int): Dictionary with the network link as key and the limit as value
- Returns:
- limit (int): Current limit for that network.
- '''
- if quota_limit:
- for net in quota_limit:
- if net['network_id'] == network_dict['network_id']:
- return net['value']
-
- if network_link in limit_dict:
- return limit_dict[network_link]
- else:
- if 'default_value' in limit_dict:
- return limit_dict['default_value']
- else:
- print(f"Error: Couldn't find limit for {network_link}")
-
- return 0
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/peerings.py b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/peerings.py
deleted file mode 100644
index 616c7f663..000000000
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/peerings.py
+++ /dev/null
@@ -1,179 +0,0 @@
-#
-# 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.
-#
-
-import time
-
-from . import metrics, networks, limits
-
-
-def get_vpc_peering_data(config, metrics_dict, limit_dict):
- '''
- Gets the data for VPC peerings (active or not) and writes it to the metric defined (vpc_peering_active_metric and vpc_peering_metric).
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- metrics_dict (dictionary of dictionary of string: string): metrics names and descriptions
- limit_dict (dictionary of string:int): Dictionary with the network link as key and the limit as value
- Returns:
- None
- '''
- timestamp = time.time()
- for project in config["monitored_projects"]:
- active_vpc_peerings, vpc_peerings = gather_vpc_peerings_data(
- config, project, limit_dict)
-
- for peering in active_vpc_peerings:
- metric_labels = {
- 'project': project,
- 'network_name': peering['network_name']
- }
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]
- ["vpc_peering_active_per_network"]["usage"]["name"],
- peering['active_peerings'], metric_labels, timestamp=timestamp)
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]
- ["vpc_peering_active_per_network"]["limit"]["name"],
- peering['network_limit'], metric_labels, timestamp=timestamp)
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]
- ["vpc_peering_active_per_network"]["utilization"]["name"],
- peering['active_peerings'] / peering['network_limit'], metric_labels,
- timestamp=timestamp)
- print(
- "Buffered number of active VPC peerings to custom metric for project:",
- project)
-
- for peering in vpc_peerings:
- metric_labels = {
- 'project': project,
- 'network_name': peering['network_name']
- }
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]["vpc_peering_per_network"]
- ["usage"]["name"], peering['peerings'], metric_labels,
- timestamp=timestamp)
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]["vpc_peering_per_network"]
- ["limit"]["name"], peering['network_limit'], metric_labels,
- timestamp=timestamp)
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]["vpc_peering_per_network"]
- ["utilization"]["name"],
- peering['peerings'] / peering['network_limit'], metric_labels,
- timestamp=timestamp)
- print("Buffered number of VPC peerings to custom metric for project:",
- project)
-
-
-def gather_peering_data(config, project_id):
- '''
- Returns a dictionary of all peerings for all networks in a project.
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- project_id (string): Project ID for the project containing the networks.
- Returns:
- network_list (dictionary of string: string): Contains the project_id, network_name(s) and network_id(s) of peered networks.
- '''
- request = config["clients"]["discovery_client"].networks().list(
- project=project_id)
- response = request.execute()
-
- network_list = []
- if 'items' in response:
- for network in response['items']:
- net = {
- 'project_id': project_id,
- 'network_name': network['name'],
- 'network_id': network['id'],
- 'peerings': []
- }
- if 'peerings' in network:
- STATE = network['peerings'][0]['state']
- if STATE == "ACTIVE":
- for peered_network in network[
- 'peerings']: # "projects/{project_name}/global/networks/{network_name}"
- start = peered_network['network'].find("projects/") + len(
- 'projects/')
- end = peered_network['network'].find("/global")
- peered_project = peered_network['network'][start:end]
- peered_network_name = peered_network['network'].split(
- "networks/")[1]
- peered_net = {
- 'project_id':
- peered_project,
- 'network_name':
- peered_network_name,
- 'network_id':
- networks.get_network_id(config, peered_project,
- peered_network_name)
- }
- net["peerings"].append(peered_net)
- network_list.append(net)
- return network_list
-
-
-def gather_vpc_peerings_data(config, project_id, limit_dict):
- '''
- Gets the data for all VPC peerings (active or not) in project_id and writes it to the metric defined in vpc_peering_active_metric and vpc_peering_metric.
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- project_id (string): We will take all VPCs in that project_id and look for all peerings to these VPCs.
- limit_dict (dictionary of string:int): Dictionary with the network link as key and the limit as value
- Returns:
- active_peerings_dict (dictionary of string: string): Contains project_id, network_name, network_limit for each active VPC peering.
- peerings_dict (dictionary of string: string): Contains project_id, network_name, network_limit for each VPC peering.
- '''
- active_peerings_dict = []
- peerings_dict = []
- request = config["clients"]["discovery_client"].networks().list(
- project=project_id)
- response = request.execute()
- if 'items' in response:
- for network in response['items']:
- if 'peerings' in network:
- STATE = network['peerings'][0]['state']
- if STATE == "ACTIVE":
- active_peerings_count = len(network['peerings'])
- else:
- active_peerings_count = 0
-
- peerings_count = len(network['peerings'])
- else:
- peerings_count = 0
- active_peerings_count = 0
-
- network_link = f"https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network['name']}"
- network_limit = limits.get_ppg(network_link, limit_dict)
-
- active_d = {
- 'project_id': project_id,
- 'network_name': network['name'],
- 'active_peerings': active_peerings_count,
- 'network_limit': network_limit
- }
- active_peerings_dict.append(active_d)
- d = {
- 'project_id': project_id,
- 'network_name': network['name'],
- 'peerings': peerings_count,
- 'network_limit': network_limit
- }
- peerings_dict.append(d)
-
- return active_peerings_dict, peerings_dict
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/routers.py b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/routers.py
deleted file mode 100644
index 064354e7f..000000000
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/routers.py
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-# 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.
-#
-
-from google.protobuf import field_mask_pb2
-
-
-def get_routers(config):
- '''
- Returns a dictionary of all Cloud Routers in the GCP organization.
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- Returns:
- routers_dict (dictionary of string: list of string): Key is the network link and value is a list of router links.
- '''
-
- read_mask = field_mask_pb2.FieldMask()
- read_mask.FromJsonString('name,versionedResources')
-
- routers_dict = {}
-
- response = config["clients"]["asset_client"].search_all_resources(
- request={
- "scope": f"organizations/{config['organization']}",
- "asset_types": ["compute.googleapis.com/Router"],
- "read_mask": read_mask,
- "page_size": config["page_size"],
- })
- for resource in response:
- network_link = None
- router_link = None
- for versioned in resource.versioned_resources:
- for field_name, field_value in versioned.resource.items():
- if field_name == "network":
- network_link = field_value
- if field_name == "selfLink":
- router_link = field_value
-
- if network_link in routers_dict:
- routers_dict[network_link].append(router_link)
- else:
- routers_dict[network_link] = [router_link]
-
- return routers_dict
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/routes.py b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/routes.py
deleted file mode 100644
index a16145454..000000000
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/routes.py
+++ /dev/null
@@ -1,289 +0,0 @@
-#
-# 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.
-#
-
-import time
-
-from collections import defaultdict
-from google.protobuf import field_mask_pb2
-from . import metrics, networks, limits, peerings, routers
-
-
-def get_routes_for_router(config, project_id, router_region, router_name):
- '''
- Returns the same of dynamic routes learned by a specific Cloud Router instance
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- project_id (string): Project ID for the project containing the Cloud Router.
- router_region (string): GCP region for the Cloud Router.
- router_name (string): Cloud Router name.
- Returns:
- sum_routes (int): Number of dynamic routes learned by the Cloud Router.
- '''
- request = config["clients"]["discovery_client"].routers().getRouterStatus(
- project=project_id, region=router_region, router=router_name)
- response = request.execute()
-
- sum_routes = 0
-
- if 'result' in response:
- if 'bgpPeerStatus' in response['result']:
- for peer in response['result']['bgpPeerStatus']:
- sum_routes += peer['numLearnedRoutes']
-
- return sum_routes
-
-
-def get_routes_for_network(config, network_link, project_id, routers_dict):
- '''
- Returns a the number of dynamic routes for a given network
-
- Parameters:
- config (dict): The dict containing config like clients and limits
- network_link (string): Network self link.
- project_id (string): Project ID containing the network.
- routers_dict (dictionary of string: list of string): Dictionary with key as network link and value as list of router links.
- Returns:
- sum_routes (int): Number of routes in that network.
- '''
- sum_routes = 0
-
- if network_link in routers_dict:
- for router_link in routers_dict[network_link]:
- # Router link is using the following format:
- # 'https://www.googleapis.com/compute/v1/projects/PROJECT_ID/regions/REGION/routers/ROUTER_NAME'
- start = router_link.find("/regions/") + len("/regions/")
- end = router_link.find("/routers/")
- router_region = router_link[start:end]
- router_name = router_link.split('/routers/')[1]
- routes = get_routes_for_router(config, project_id, router_region,
- router_name)
-
- sum_routes += routes
-
- return sum_routes
-
-
-def get_dynamic_routes(config, metrics_dict, limits_dict):
- '''
- This function gets the usage, limit and utilization for the dynamic routes per VPC
- note: assumes global routing is ON for all VPCs
- Parameters:
- config (dict): The dict containing config like clients and limits
- metrics_dict (dictionary of dictionary of string: string): metrics names and descriptions.
- limits_dict (dictionary of string: int): key is network link (or 'default_value') and value is the limit for that network
- Returns:
- dynamic_routes_dict (dictionary of string: int): key is network link and value is the number of dynamic routes for that network
- '''
- routers_dict = routers.get_routers(config)
- dynamic_routes_dict = defaultdict(int)
-
- timestamp = time.time()
- for project in config["monitored_projects"]:
- network_dict = networks.get_networks(config, project)
-
- for net in network_dict:
- sum_routes = get_routes_for_network(config, net['self_link'], project,
- routers_dict)
- dynamic_routes_dict[net['self_link']] = sum_routes
-
- if net['self_link'] in limits_dict:
- limit = limits_dict[net['self_link']]
- else:
- if 'default_value' in limits_dict:
- limit = limits_dict['default_value']
- else:
- print("Error: couldn't find limit for dynamic routes.")
- break
-
- utilization = sum_routes / limit
- metric_labels = {'project': project, 'network_name': net['network_name']}
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]
- ["dynamic_routes_per_network"]["usage"]["name"], sum_routes,
- metric_labels, timestamp=timestamp)
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]
- ["dynamic_routes_per_network"]["limit"]["name"], limit, metric_labels,
- timestamp=timestamp)
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]
- ["dynamic_routes_per_network"]["utilization"]["name"], utilization,
- metric_labels, timestamp=timestamp)
-
- print("Buffered metrics for dynamic routes for VPCs in project", project)
-
- return dynamic_routes_dict
-
-
-def get_routes_ppg(config, metric_dict, usage_dict, limit_dict):
- '''
- This function gets the usage, limit and utilization for the static or dynamic routes per VPC peering group.
- note: assumes global routing is ON for all VPCs for dynamic routes, assumes share custom routes is on for all peered networks
- Parameters:
- config (dict): The dict containing config like clients and limits
- metric_dict (dictionary of string: string): Dictionary with the metric names and description, that will be used to populate the metrics
- usage_dict (dictionnary of string:int): Dictionary with the network link as key and the number of resources as value
- limit_dict (dictionary of string:int): Dictionary with the network link as key and the limit as value
- Returns:
- None
- '''
- timestamp = time.time()
- for project_id in config["monitored_projects"]:
- network_dict_list = peerings.gather_peering_data(config, project_id)
-
- for network_dict in network_dict_list:
- network_link = f"https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network_dict['network_name']}"
-
- limit = limits.get_ppg(network_link, limit_dict)
-
- usage = 0
- if network_link in usage_dict:
- usage = usage_dict[network_link]
-
- # Here we add usage and limit to the network dictionary
- network_dict["usage"] = usage
- network_dict["limit"] = limit
-
- # For every peered network, get usage and limits
- for peered_network_dict in network_dict['peerings']:
- peered_network_link = f"https://www.googleapis.com/compute/v1/projects/{peered_network_dict['project_id']}/global/networks/{peered_network_dict['network_name']}"
- peered_usage = 0
- if peered_network_link in usage_dict:
- peered_usage = usage_dict[peered_network_link]
-
- peered_limit = limits.get_ppg(peered_network_link, limit_dict)
-
- # Here we add usage and limit to the peered network dictionary
- peered_network_dict["usage"] = peered_usage
- peered_network_dict["limit"] = peered_limit
-
- limits.count_effective_limit(config, project_id, network_dict,
- metric_dict["usage"]["name"],
- metric_dict["limit"]["name"],
- metric_dict["utilization"]["name"],
- limit_dict, timestamp)
- print(
- f"Buffered {metric_dict['usage']['name']} for peering group {network_dict['network_name']} in {project_id}"
- )
-
-
-def get_static_routes_dict(config):
- '''
- Calls the Asset Inventory API to get all static custom routes under the GCP organization.
- Parameters:
- config (dict): The dict containing config like clients and limits
- Returns:
- routes_per_vpc_dict (dictionary of string: int): Keys are the network links and values are the number of custom static routes per network.
- '''
- routes_per_vpc_dict = defaultdict()
- usage_dict = defaultdict()
-
- read_mask = field_mask_pb2.FieldMask()
- read_mask.FromJsonString('name,versionedResources')
-
- response = config["clients"]["asset_client"].search_all_resources(
- request={
- "scope": f"organizations/{config['organization']}",
- "asset_types": ["compute.googleapis.com/Route"],
- "read_mask": read_mask
- })
-
- for resource in response:
- for versioned in resource.versioned_resources:
- static_route = dict()
- for field_name, field_value in versioned.resource.items():
- static_route[field_name] = field_value
- static_route["project_id"] = static_route["network"].split('/')[6]
- static_route["network_name"] = static_route["network"].split('/')[-1]
- network_link = f"https://www.googleapis.com/compute/v1/projects/{static_route['project_id']}/global/networks/{static_route['network_name']}"
- #exclude default vpc and peering routes, dynamic routes are not in Cloud Asset Inventory
- if "nextHopPeering" not in static_route and "nextHopNetwork" not in static_route:
- if network_link not in routes_per_vpc_dict:
- routes_per_vpc_dict[network_link] = dict()
- routes_per_vpc_dict[network_link]["project_id"] = static_route[
- "project_id"]
- routes_per_vpc_dict[network_link]["network_name"] = static_route[
- "network_name"]
- if static_route["destRange"] not in routes_per_vpc_dict[network_link]:
- routes_per_vpc_dict[network_link][static_route["destRange"]] = {}
- if "usage" not in routes_per_vpc_dict[network_link]:
- routes_per_vpc_dict[network_link]["usage"] = 0
- routes_per_vpc_dict[network_link][
- "usage"] = routes_per_vpc_dict[network_link]["usage"] + 1
-
- #output a dict with network links and usage only
- return {
- network_link_out: routes_per_vpc_dict[network_link_out]["usage"]
- for network_link_out in routes_per_vpc_dict
- }
-
-
-def get_static_routes_data(config, metrics_dict, static_routes_dict,
- project_quotas_dict):
- '''
- Determines and writes the number of static routes for each VPC in monitored projects, the per project limit and the per project utilization
- note: assumes custom routes sharing is ON for all VPCs
- Parameters:
- config (dict): The dict containing config like clients and limits
- metric_dict (dictionary of string: string): Dictionary with the metric names and description, that will be used to populate the metrics
- static_routes_dict (dictionary of dictionary: int): Keys are the network links and values are the number of custom static routes per network.
- project_quotas_dict (dictionary of string:int): Dictionary with the network link as key and the limit as value.
- Returns:
- None
- '''
- timestamp = time.time()
- project_usage = {project: 0 for project in config["monitored_projects"]}
-
- #usage is drilled down by network
- for network_link in static_routes_dict:
-
- project_id = network_link.split('/')[6]
- if (project_id not in config["monitored_projects"]):
- continue
- network_name = network_link.split('/')[-1]
-
- project_usage[project_id] = project_usage[project_id] + static_routes_dict[
- network_link]
-
- metric_labels = {"project": project_id, "network_name": network_name}
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]["static_routes_per_project"]
- ["usage"]["name"], static_routes_dict[network_link], metric_labels,
- timestamp=timestamp)
-
- #limit and utilization are calculated by project
- for project_id in project_usage:
- current_quota_limit = project_quotas_dict[project_id]['global']["routes"][
- "limit"]
- if current_quota_limit is None:
- print(
- f"Could not determine static routes metric for projects/{project_id} due to missing quotas"
- )
- continue
- # limit and utilization are calculted by project
- metric_labels = {"project": project_id}
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]["static_routes_per_project"]
- ["limit"]["name"], current_quota_limit, metric_labels,
- timestamp=timestamp)
- metrics.append_data_to_series_buffer(
- config, metrics_dict["metrics_per_network"]["static_routes_per_project"]
- ["utilization"]["name"],
- project_usage[project_id] / current_quota_limit, metric_labels,
- timestamp=timestamp)
-
- return
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/secondarys.py b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/secondarys.py
deleted file mode 100644
index 6030ddafd..000000000
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/secondarys.py
+++ /dev/null
@@ -1,266 +0,0 @@
-#
-# 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.
-#
-
-import time
-
-from . import metrics
-from google.protobuf import field_mask_pb2
-from google.protobuf.json_format import MessageToDict
-import ipaddress
-
-
-def get_all_secondaryRange(config):
- '''
- Returns a dictionary with secondary range informations
- Parameters:
- config (dict): The dict containing config like clients and limits
- Returns:
- secondary_dict (dictionary of String: dictionary): Key is the project_id,
- value is a nested dictionary with subnet_name/secondary_range_name as the key.
- '''
- secondary_dict = {}
- read_mask = field_mask_pb2.FieldMask()
- read_mask.FromJsonString('name,versionedResources')
-
- response = config["clients"]["asset_client"].search_all_resources(
- request={
- "scope": f"organizations/{config['organization']}",
- "asset_types": ['compute.googleapis.com/Subnetwork'],
- "read_mask": read_mask,
- "page_size": config["page_size"],
- })
-
- for asset in response:
- for versioned in asset.versioned_resources:
- subnet_name = versioned.resource.get('name')
- # Network self link format:
- # "https://www.googleapis.com/compute/v1/projects/
+
+## Project and function-level configuration
+
+A single project is used both for deploying the function and to collect generated timeseries: writing timeseries to a separate project is not supported here for brevity, but is very simple to implement (basically change the value for `op_project` in the schedule payload queued in PubSub). The project is configured with the required APIs, and it can also optionally be created via the `project_create_config` variable.
+
+The function uses a dedicated service account which is created for this purpose. Roles to allow discovery can optionally be set at the top-level discovery scope (organization or folder) via the `grant_discovery_iam_roles` variable, those of course require the right set of permissions on the part of the identity running `terraform apply`. The alternative when IAM bindings cannot be managed on the top-level scope, is to assign `roles/compute.viewer` and `roles/cloudasset.viewer` to the function service account from a separate process, or manually in the console.
+
+A few configuration values for the function which are relevant to this example can also be configured in the `cloud_function_config` variable, particularly the `debug` attribute which turns on verbose logging to help in troubleshooting.
+
+## Discovery configuration
+
+Discovery configuration is done via the `discovery_config` variable, which mimicks the set of options available when running the discovery tool in cli mode. Pay particular care in defining the right top-level scope via the `discovery_root` attribute, as this is the root of the hierarchy used to discover Compute resources and it needs to include the individual folders and projects that needs to be monitored, which are defined via the `monitored_folders` and `monitored_projects` attributes.
+
+The following schematic diagram of a resource hierarchy illustrates the interplay between root scope and monitored resources. The root scope is set to the top-level red folder and completely encloses every resource that needs to be monitored. The blue folder and project are set as monitored defining the actual perimeter used to discover resources. Note that setting the root scope to the blue folder would have resulted in the rightmost project being excluded.
+
+
+
+This is an example of a working configuration, where the discovery root is set at the org level, but resources used to compute timeseries need to be part of the hierarchy of two specific folders:
+
+```tfvars
+# cloud_function_config = {
+# debug = true
+# }
+discovery_config = {
+ discovery_root = "organizations/1234567890"
+ monitored_folders = ["3456789012", "7890123456"]
+ monitored_projects = []
+ # if you have custom quota not returned by the API, compile a file and set
+ # its pat here; format is described in ../src/custom-quotas.sample
+ # custom_quota_file = "../src/custom-quotas.yaml"
+}
+grant_discovery_iam_roles = true
+project_create_config = {
+ billing_account_id = "12345-ABCDEF-12345"
+ parent_id = "folders/2345678901"
+}
+project_id = "my-project"
+```
+
+## Manual triggering for troubleshooting
+
+If the function crashes or its behaviour is not as expected, you can turn on debugging via the `cloud_function_config.debug` variable attribute, then manually trigger the function from the console by specifying a payload with a single `data` attribute containing the base64-encoded arguments passed to the function by Cloud Scheduler. You can get the pre-computed payload from the `troubleshooting_payload` output:
+
+```bash
+# copy and paste to the function's "Testing" tab in the console
+tf output -raw troubleshooting_payload
+```
+
+## Monitoring dashboard
+
+A monitoring dashboard can be optionally be deployed int he same project by setting the `dashboard_json_path` variable to the path of a dashboard JSON file. A sample dashboard is in included, and can be deployed with this variable configuration:
+
+```tfvars
+dashboard_json_path = "../dashboards/quotas-utilization.json"
+```
+
+
+## Variables
+
+| name | description | type | required | default |
+|---|---|:---:|:---:|:---:|
+| [discovery_config](variables.tf#L44) | Discovery configuration. Discovery root is the organization or a folder. If monitored folders and projects are empy, every project under the discovery root node will be monitored. | object({…}) | ✓ | |
+| [project_id](variables.tf#L90) | Project id where the Cloud Function will be deployed. | string | ✓ | |
+| [bundle_path](variables.tf#L17) | Path used to write the intermediate Cloud Function code bundle. | string | | "./bundle.zip" |
+| [cloud_function_config](variables.tf#L23) | Optional Cloud Function configuration. | object({…}) | | {} |
+| [dashboard_json_path](variables.tf#L38) | Optional monitoring dashboard to deploy. | string | | null |
+| [grant_discovery_iam_roles](variables.tf#L62) | Optionally grant required IAM roles to Cloud Function service account. | bool | | false |
+| [labels](variables.tf#L69) | Billing labels used for the Cloud Function, and the project if project_create is true. | map(string) | | {} |
+| [name](variables.tf#L75) | Name used to create Cloud Function related resources. | string | | "net-dash" |
+| [project_create_config](variables.tf#L81) | Optional configuration if project creation is required. | object({…}) | | null |
+| [region](variables.tf#L95) | Compute region where the Cloud Function will be deployed. | string | | "europe-west1" |
+| [schedule_config](variables.tf#L101) | Schedule timer configuration in crontab format. | string | | "*/30 * * * *" |
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| [bucket](outputs.tf#L17) | Cloud Function deployment bucket resource. | |
+| [cloud-function](outputs.tf#L22) | Cloud Function resource. | |
+| [project_id](outputs.tf#L27) | Project id. | |
+| [service_account](outputs.tf#L32) | Cloud Function service account. | |
+| [troubleshooting_payload](outputs.tf#L40) | Cloud Function payload used for manual triggering. | ✓ |
+
+
diff --git a/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/diagram-scope.png b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/diagram-scope.png
new file mode 100644
index 000000000..6247c1c90
Binary files /dev/null and b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/diagram-scope.png differ
diff --git a/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/diagram.png b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/diagram.png
new file mode 100644
index 000000000..d71540672
Binary files /dev/null and b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/diagram.png differ
diff --git a/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/main.tf b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/main.tf
new file mode 100644
index 000000000..abbea80e2
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/main.tf
@@ -0,0 +1,144 @@
+/**
+ * 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 {
+ discovery_roles = ["roles/compute.viewer", "roles/cloudasset.viewer"]
+}
+
+resource "random_string" "default" {
+ count = var.cloud_function_config.bucket_name == null ? 1 : 0
+ length = 8
+ special = false
+ upper = false
+}
+
+module "project" {
+ source = "../../../../modules/project"
+ name = var.project_id
+ billing_account = try(var.project_create_config.billing_account_id, null)
+ labels = var.project_create_config != null ? var.labels : null
+ parent = try(var.project_create_config.parent_id, null)
+ project_create = var.project_create_config != null
+ services = [
+ "cloudasset.googleapis.com",
+ "cloudbuild.googleapis.com",
+ "cloudfunctions.googleapis.com",
+ "cloudscheduler.googleapis.com",
+ "compute.googleapis.com",
+ "monitoring.googleapis.com"
+ ]
+}
+
+module "pubsub" {
+ source = "../../../../modules/pubsub"
+ project_id = module.project.project_id
+ name = var.name
+ regions = [var.region]
+ subscriptions = { "${var.name}-default" = null }
+}
+
+module "cloud-function" {
+ source = "../../../../modules/cloud-function"
+ project_id = module.project.project_id
+ name = var.name
+ bucket_name = coalesce(
+ var.cloud_function_config.bucket_name,
+ "${var.name}-${random_string.default.0.id}"
+ )
+ bucket_config = {
+ location = var.region
+ }
+ build_worker_pool = var.cloud_function_config.build_worker_pool_id
+ bundle_config = {
+ source_dir = var.cloud_function_config.source_dir
+ output_path = var.cloud_function_config.bundle_path
+ }
+ environment_variables = (
+ var.cloud_function_config.debug != true ? {} : { DEBUG = "1" }
+ )
+ function_config = {
+ entry_point = "main_cf_pubsub"
+ memory_mb = var.cloud_function_config.memory_mb
+ timeout_seconds = var.cloud_function_config.timeout_seconds
+ }
+ service_account_create = true
+ trigger_config = {
+ v1 = {
+ event = "google.pubsub.topic.publish"
+ resource = module.pubsub.topic.id
+ }
+ }
+}
+
+resource "google_cloud_scheduler_job" "default" {
+ project = var.project_id
+ region = var.region
+ name = var.name
+ schedule = var.schedule_config
+ time_zone = "UTC"
+
+ pubsub_target {
+ attributes = {}
+ topic_name = module.pubsub.topic.id
+ data = base64encode(jsonencode({
+ discovery_root = var.discovery_config.discovery_root
+ folders = var.discovery_config.monitored_folders
+ projects = var.discovery_config.monitored_projects
+ monitoring_project = module.project.project_id
+ custom_quota = (
+ var.discovery_config.custom_quota_file == null
+ ? { networks = {}, projects = {} }
+ : yamldecode(file(var.discovery_config.custom_quota_file))
+ )
+ }))
+ }
+}
+
+resource "google_organization_iam_member" "discovery" {
+ for_each = toset(
+ var.grant_discovery_iam_roles &&
+ startswith(var.discovery_config.discovery_root, "organizations/")
+ ? local.discovery_roles
+ : []
+ )
+ org_id = split("/", var.discovery_config.discovery_root)[1]
+ role = each.key
+ member = module.cloud-function.service_account_iam_email
+}
+
+resource "google_folder_iam_member" "discovery" {
+ for_each = toset(
+ var.grant_discovery_iam_roles &&
+ startswith(var.discovery_config.discovery_root, "folders/")
+ ? local.discovery_roles
+ : []
+ )
+ folder = var.discovery_config.discovery_root
+ role = each.key
+ member = module.cloud-function.service_account_iam_email
+}
+
+resource "google_project_iam_member" "monitoring" {
+ project = module.project.project_id
+ role = "roles/monitoring.metricWriter"
+ member = module.cloud-function.service_account_iam_email
+}
+
+resource "google_monitoring_dashboard" "dashboard" {
+ count = var.dashboard_json_path == null ? 0 : 1
+ project = var.project_id
+ dashboard_json = file(var.dashboard_json_path)
+}
diff --git a/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/outputs.tf b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/outputs.tf
new file mode 100644
index 000000000..0c2c50abe
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/outputs.tf
@@ -0,0 +1,46 @@
+/**
+ * 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.
+ */
+
+output "bucket" {
+ description = "Cloud Function deployment bucket resource."
+ value = module.cloud-function.bucket
+}
+
+output "cloud-function" {
+ description = "Cloud Function resource."
+ value = module.cloud-function.function
+}
+
+output "project_id" {
+ description = "Project id."
+ value = module.project.project_id
+}
+
+output "service_account" {
+ description = "Cloud Function service account."
+ value = {
+ email = module.cloud-function.service_account_email
+ iam_email = module.cloud-function.service_account_iam_email
+ }
+}
+
+output "troubleshooting_payload" {
+ description = "Cloud Function payload used for manual triggering."
+ sensitive = true
+ value = jsonencode({
+ data = google_cloud_scheduler_job.default.pubsub_target.0.data
+ })
+}
diff --git a/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/variables.tf b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/variables.tf
new file mode 100644
index 000000000..680b689dd
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/variables.tf
@@ -0,0 +1,105 @@
+/**
+ * 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 "bundle_path" {
+ description = "Path used to write the intermediate Cloud Function code bundle."
+ type = string
+ default = "./bundle.zip"
+}
+
+variable "cloud_function_config" {
+ description = "Optional Cloud Function configuration."
+ type = object({
+ bucket_name = optional(string)
+ build_worker_pool_id = optional(string)
+ bundle_path = optional(string, "./bundle.zip")
+ debug = optional(bool, false)
+ memory_mb = optional(number, 256)
+ source_dir = optional(string, "../src")
+ timeout_seconds = optional(number, 540)
+ })
+ default = {}
+ nullable = false
+}
+
+variable "dashboard_json_path" {
+ description = "Optional monitoring dashboard to deploy."
+ type = string
+ default = null
+}
+
+variable "discovery_config" {
+ description = "Discovery configuration. Discovery root is the organization or a folder. If monitored folders and projects are empy, every project under the discovery root node will be monitored."
+ type = object({
+ discovery_root = string
+ monitored_folders = list(string)
+ monitored_projects = list(string)
+ custom_quota_file = optional(string)
+ })
+ nullable = false
+ validation {
+ condition = (
+ var.discovery_config.monitored_folders != null &&
+ var.discovery_config.monitored_projects != null
+ )
+ error_message = "Monitored folders and projects can be empty lists, but they cannot be null."
+ }
+}
+
+variable "grant_discovery_iam_roles" {
+ description = "Optionally grant required IAM roles to Cloud Function service account."
+ type = bool
+ default = false
+ nullable = false
+}
+
+variable "labels" {
+ description = "Billing labels used for the Cloud Function, and the project if project_create is true."
+ type = map(string)
+ default = {}
+}
+
+variable "name" {
+ description = "Name used to create Cloud Function related resources."
+ type = string
+ default = "net-dash"
+}
+
+variable "project_create_config" {
+ description = "Optional configuration if project creation is required."
+ type = object({
+ billing_account_id = string
+ parent_id = optional(string)
+ })
+ default = null
+}
+
+variable "project_id" {
+ description = "Project id where the Cloud Function will be deployed."
+ type = string
+}
+
+variable "region" {
+ description = "Compute region where the Cloud Function will be deployed."
+ type = string
+ default = "europe-west1"
+}
+
+variable "schedule_config" {
+ description = "Schedule timer configuration in crontab format."
+ type = string
+ default = "*/30 * * * *"
+}
diff --git a/blueprints/cloud-operations/network-dashboard/main.tf b/blueprints/cloud-operations/network-dashboard/main.tf
deleted file mode 100644
index e74cabd6e..000000000
--- a/blueprints/cloud-operations/network-dashboard/main.tf
+++ /dev/null
@@ -1,191 +0,0 @@
-/**
- * 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 {
- project_ids = toset(var.monitored_projects_list)
- projects = join(",", local.project_ids)
-
- folder_ids = toset(var.monitored_folders_list)
- folders = join(",", local.folder_ids)
- monitoring_project = var.monitoring_project_id == "" ? module.project-monitoring[0].project_id : var.monitoring_project_id
-}
-
-################################################
-# Monitoring project creation #
-################################################
-
-module "project-monitoring" {
- count = var.monitoring_project_id == "" ? 1 : 0
- source = "../../../modules/project"
- name = "network-dashboards"
- parent = "organizations/${var.organization_id}"
- prefix = var.prefix
- billing_account = var.billing_account
- services = var.project_monitoring_services
-}
-
-################################################
-# Service account creation and IAM permissions #
-################################################
-
-module "service-account-function" {
- source = "../../../modules/iam-service-account"
- project_id = local.monitoring_project
- name = "sa-dash"
- generate_key = false
-
- # Required IAM permissions for this service account are:
- # 1) compute.networkViewer on projects to be monitored (I gave it at organization level for now for simplicity)
- # 2) monitoring viewer on the projects to be monitored (I gave it at organization level for now for simplicity)
-
- iam_organization_roles = {
- "${var.organization_id}" = [
- "roles/compute.networkViewer",
- "roles/monitoring.viewer",
- "roles/cloudasset.viewer"
- ]
- }
-
- iam_project_roles = {
- "${local.monitoring_project}" = [
- "roles/monitoring.metricWriter",
- ]
- }
-}
-
-module "service-account-scheduler" {
- source = "../../../modules/iam-service-account"
- project_id = local.monitoring_project
- name = "sa-scheduler"
- generate_key = false
-
- iam_project_roles = {
- "${local.monitoring_project}" = [
- "roles/run.invoker",
- "roles/cloudfunctions.invoker"
- ]
- }
-}
-
-################################################
-# Cloud Function configuration (& Scheduler) #
-# you can comment out the pub/sub call in case of 2nd generation function
-################################################
-
-module "pubsub" {
-
- source = "../../../modules/pubsub"
- project_id = local.monitoring_project
- name = "network-dashboard-pubsub"
- subscriptions = {
- "network-dashboard-pubsub-default" = null
- }
- # the Cloud Scheduler robot service account already has pubsub.topics.publish
- # at the project level via roles/cloudscheduler.serviceAgent
-}
-
-resource "google_cloud_scheduler_job" "job" {
- count = var.cf_version == "V2" ? 0 : 1
- project = local.monitoring_project
- region = var.region
- name = "network-dashboard-scheduler"
- schedule = var.schedule_cron
- time_zone = "UTC"
-
- pubsub_target {
- topic_name = module.pubsub.topic.id
- data = base64encode("test")
- }
-}
-#http trigger for 2nd generation function
-
-resource "google_cloud_scheduler_job" "job_httptrigger" {
- count = var.cf_version == "V2" ? 1 : 0
- project = local.monitoring_project
- region = var.region
- name = "network-dashboard-scheduler"
- schedule = var.schedule_cron
- time_zone = "UTC"
-
- http_target {
- http_method = "POST"
- uri = module.cloud-function.uri
-
- oidc_token {
- service_account_email = module.service-account-scheduler.email
- }
- }
-}
-
-module "cloud-function" {
- v2 = var.cf_version == "V2"
- source = "../../../modules/cloud-function"
- project_id = local.monitoring_project
- name = "network-dashboard-cloud-function"
- bucket_name = "${local.monitoring_project}-network-dashboard-bucket"
- bucket_config = {
- location = var.region
- }
- region = var.region
-
- bundle_config = {
- source_dir = "cloud-function"
- output_path = "cloud-function.zip"
- }
-
- function_config = {
- timeout = 480 # Timeout in seconds, increase it if your CF timeouts and use v2 if > 9 minutes.
- entry_point = "main"
- runtime = "python39"
- instances = 1
- memory_mb = 256
-
- }
-
- environment_variables = {
- MONITORED_PROJECTS_LIST = local.projects
- MONITORED_FOLDERS_LIST = local.folders
- MONITORING_PROJECT_ID = local.monitoring_project
- ORGANIZATION_ID = var.organization_id
- CF_VERSION = var.cf_version
- }
-
- service_account = module.service-account-function.email
- # Internal only doesn't seem to work with CFv2:
- ingress_settings = var.cf_version == "V2" ? "ALLOW_ALL" : "ALLOW_INTERNAL_ONLY"
-
- trigger_config = var.cf_version == "V2" ? {
- v2 = {
- event_type = "google.cloud.pubsub.topic.v1.messagePublished"
- pubsub_topic = module.pubsub.topic.id
- service_account_create = true
- }
- } : {
- v1 = {
- event = "google.pubsub.topic.publish"
- resource = module.pubsub.topic.id
- }
- }
-}
-
-################################################
-# Cloud Monitoring Dashboard creation #
-################################################
-
-resource "google_monitoring_dashboard" "dashboard" {
- dashboard_json = file("${path.module}/dashboards/quotas-utilization.json")
- project = local.monitoring_project
-}
diff --git a/blueprints/cloud-operations/network-dashboard/src/README.md b/blueprints/cloud-operations/network-dashboard/src/README.md
new file mode 100644
index 000000000..a7fad8217
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/README.md
@@ -0,0 +1,111 @@
+# Network Dashboard Discovery Tool
+
+This tool constitutes the discovery and data gathering side of the Network Dashboard, and can be used in combination with the related [Terraform deployment examples](../), or packaged in different ways including standalone manual use.
+
+- [Quick Usage Example](#quick-usage-example)
+- [High Level Architecture and Plugin Design](#high-level-architecture-and-plugin-design)
+- [Debugging and Troubleshooting](#debugging-and-troubleshooting)
+
+## Quick Usage Example
+
+The tool behaves like a regular CLI app, with several options documented via the usual short help:
+
+```text
+./main.py --help
+
+Usage: main.py [OPTIONS]
+
+ CLI entry point.
+
+Options:
+ -dr, --discovery-root TEXT Root node for asset discovery,
+ organizations/nnn or folders/nnn. [required]
+ -mon, --monitoring-project TEXT GCP monitoring project where metrics will be
+ stored. [required]
+ -p, --project TEXT GCP project id to be monitored, can be specified multiple
+ times.
+ -f, --folder INTEGER GCP folder id to be monitored, can be specified multiple
+ times.
+ --custom-quota-file FILENAME Custom quota file in yaml format.
+ --dump-file FILENAME Export JSON representation of resources to
+ file.
+ --load-file FILENAME Load JSON resources from file, skips init and
+ discovery.
+ --debug-plugin TEXT Run only core and specified timeseries plugin.
+ --help Show this message and exit.
+```
+
+In normal use three pieces of information need to be passed in:
+
+- the monitoring project where metric descriptors and timeseries will be stored
+- the discovery root scope (organization or top-level folder, [see here for examples](../deploy-cloud-function/README.md#discovery-configuration))
+- the list of folders and/or projects that contain the resources to be monitored (folders will discover all included projects)
+
+To account for custom quota which are not yet exposed via API or which are applied to individual networks, a YAML file with quota overrides can be specified via the `--custom-quota-file` option. Refer to the [included sample](./custom-quotas.sample) for details on its format.
+
+A typical invocation might look like this:
+
+```bash
+./main.py \
+ -dr organizations/1234567890 \
+ -op my-monitoring-project \
+ --folder 1234567890 --folder 987654321 \
+ --project my-net-project \
+ --custom-quota-file custom-quotas.yaml
+```
+
+## High Level Architecture and Plugin Design
+
+The tool is composed of two main processing phases
+
+- the discovery of resources within a predefined scope using Cloud Asset Inventory and Compute APIs
+- the computation of metric timeseries derived from discovered resources
+
+Once both phases are complete, the tool sends generated timeseries to Cloud Operations together with any missing metric descriptors.
+
+Every action during those phases is delegated to a series of plugins, which conform to simple interfaces and exchange predefined basic types with the main module. Plugins are registered at runtime, and are split in broad categories depending on the stage where they execute:
+
+- init plugin functions have the task of preparing the required keys in the shared resource data structure. Usually, init functions are usually small and there's one for each discovery plugin
+- discovery plugin functions do the bulk of the work of discovering resources; they return HTTP Requests (e.g. calls to GCP APIs) or Resource objects (extracted from the API responses) to the main module, and receive HTTP Responses
+- timeseries plugin read from the shared resource data structure, and return computed Metric Descriptors and Timeseries objects
+
+Plugins are registered via simple functions defined in the [plugin package initialization file](./plugins/__init__.py), and leverage [utility functions](./plugins/utils.py) for batching API requests and parsing results.
+
+The main module cycles through stages, calling stage plugins in succession iterating over their results.
+
+## Debugging and Troubleshooting
+
+Note that python version > 3.8 is required.
+
+If you run into a `ModuleNotFoundError`, install the required dependencies:
+`pip3 install -r requirements.txt`
+
+A few convenience options are provided to simplify development, debugging and troubleshooting:
+
+- the discovery phase results can be dumped to a JSON file, that can then be used to check actual resource representation, or skip the discovery phase entirely to speed up development of timeseries-related functions
+- a single timeseries plugin can be optionally run alone, to focus debugging and decrease the amount of noise from logs and outputs
+
+This is an example call that stores discovery results to a file:
+
+```bash
+./main.py \
+ -dr organizations/1234567890 \
+ -op my-monitoring-project \
+ --folder 1234567890 --folder 987654321 \
+ --project my-net-project \
+ --custom-quota-file custom-quotas.yaml \
+ --dump-file out.json
+```
+
+And this is the corresponding call that skips the discovery phase and also runs a single timeseries plugin:
+
+```bash
+./main.py \
+ -dr organizations/1234567890 \
+ -op my-monitoring-project \
+ --folder 1234567890 --folder 987654321 \
+ --project my-net-project \
+ --custom-quota-file custom-quotas.yaml \
+ --load-file out.json \
+ --debug-plugin plugins.series-firewall-rules.timeseries
+```
diff --git a/blueprints/cloud-operations/network-dashboard/src/custom-quotas.sample b/blueprints/cloud-operations/network-dashboard/src/custom-quotas.sample
new file mode 100644
index 000000000..9f090b3c5
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/custom-quotas.sample
@@ -0,0 +1,8 @@
+projects:
+ tf-playground-svpc-net:
+ global:
+ INTERNAL_FORWARDING_RULES_PER_NETWORK: 750
+networks:
+ # TODO: what are the quotas that can be overridden at the network level?
+ projects/tf-playground-svpc-net/global/networks/shared-vpc:
+ PEERINGS_PER_NETWORK: 40
diff --git a/blueprints/cloud-operations/network-dashboard/src/main.py b/blueprints/cloud-operations/network-dashboard/src/main.py
new file mode 100755
index 000000000..bd57f18e4
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/main.py
@@ -0,0 +1,300 @@
+#!/usr/bin/env python3
+# 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.
+'Network dashboard: create network-related metric timeseries for GCP resources.'
+
+import base64
+import binascii
+import collections
+import json
+import logging
+import os
+
+import click
+import google.auth
+import plugins
+import plugins.monitoring
+import yaml
+
+from google.auth.transport.requests import AuthorizedSession
+
+HTTP = AuthorizedSession(google.auth.default()[0])
+LOGGER = logging.getLogger('net-dash')
+MONITORING_ROOT = 'netmon/'
+
+Result = collections.namedtuple('Result', 'phase resource data')
+
+
+def do_discovery(resources):
+ '''Calls discovery plugin functions and collect discovered resources.
+
+ The communication with discovery plugins uses double dispatch, where plugins
+ accept either no args and return 1-n HTTP request instances, or a single HTTP
+ response and return 1-n resource instances. A queue is set up for each plugin
+ results since each call can return multiple requests or resources.
+
+ Args:
+ resources: pre-initialized map where discovered resources will be stored.
+ '''
+ LOGGER.info(f'discovery start')
+ for plugin in plugins.get_discovery_plugins():
+ # set up the queue with the initial list of HTTP requests from this plugin
+ q = collections.deque(plugin.func(resources))
+ while q:
+ result = q.popleft()
+ if isinstance(result, plugins.HTTPRequest):
+ # fetch a single HTTP request
+ response = fetch(result)
+ if not response:
+ continue
+ if result.json:
+ try:
+ # decode the JSON HTTP response and pass it to the plugin
+ LOGGER.debug(f'passing JSON result to {plugin.name}')
+ results = plugin.func(resources, response, response.json())
+ except json.decoder.JSONDecodeError as e:
+ LOGGER.critical(
+ f'error decoding JSON for {result.url}: {e.args[0]}')
+ continue
+ else:
+ # pass the raw HTTP response to the plugin
+ LOGGER.debug(f'passing raw result to {plugin.name}')
+ results = plugin.func(resources, response)
+ q += collections.deque(results)
+ elif isinstance(result, plugins.Resource):
+ # store a resource the plugin derived from a previous HTTP response
+ LOGGER.debug(f'got resource {result} from {plugin.name}')
+ if result.key:
+ # this specific resource is indexed by an additional key
+ resources[result.type][result.id][result.key] = result.data
+ else:
+ resources[result.type][result.id] = result.data
+ LOGGER.info('discovery end {}'.format(
+ {k: len(v) for k, v in resources.items() if not isinstance(v, str)}))
+
+
+def do_init(resources, discovery_root, monitoring_project, folders=None,
+ projects=None, custom_quota=None):
+ '''Calls init plugins to configure keys in the shared resource map.
+
+ Args:
+ discovery_root: root node for discovery from configuration.
+ monitoring_project: monitoring project id id from configuration.
+ folders: list of folder ids for resource discovery from configuration.
+ projects: list of project ids for resource discovery from configuration.
+ '''
+ LOGGER.info(f'init start')
+ folders = [str(f) for f in folders or []]
+ resources['config:discovery_root'] = discovery_root
+ resources['config:monitoring_project'] = monitoring_project
+ resources['config:folders'] = folders
+ resources['config:projects'] = projects or []
+ resources['config:custom_quota'] = custom_quota or {}
+ resources['config:monitoring_root'] = MONITORING_ROOT
+ if discovery_root.startswith('organization'):
+ resources['organization'] = discovery_root.split('/')[-1]
+ for f in folders:
+ resources['folders'] = {f: {} for f in folders}
+ for plugin in plugins.get_init_plugins():
+ plugin.func(resources)
+ LOGGER.info(f'init completed, resources {resources}')
+
+
+def do_timeseries_calc(resources, descriptors, timeseries, debug_plugin=None):
+ '''Calls timeseries plugins and collect resulting descriptors and timeseries.
+
+ Timeseries plugin return a list of MetricDescriptors and Timeseries instances,
+ one per each metric.
+
+ Args:
+ resources: shared map of configuration and discovered resources.
+ descriptors: list where collected descriptors will be stored.
+ timeseries: list where collected timeseries will be stored.
+ debug_plugin: optional name of a single plugin to call
+ '''
+ LOGGER.info(f'timeseries calc start (debug plugin: {debug_plugin})')
+ for plugin in plugins.get_timeseries_plugins():
+ if debug_plugin and plugin.name != debug_plugin:
+ LOGGER.info(f'skipping {plugin.name}')
+ continue
+ num_desc, num_ts = 0, 0
+ for result in plugin.func(resources):
+ if not result:
+ continue
+ # append result to the relevant collection (descriptors or timeseries)
+ if isinstance(result, plugins.MetricDescriptor):
+ descriptors.append(result)
+ num_desc += 1
+ elif isinstance(result, plugins.TimeSeries):
+ timeseries.append(result)
+ num_ts += 1
+ LOGGER.info(f'{plugin.name}: {num_desc} descriptors {num_ts} timeseries')
+ LOGGER.info('timeseries calc end (descriptors: {} timeseries: {})'.format(
+ len(descriptors), len(timeseries)))
+
+
+def do_timeseries_descriptors(project_id, existing, computed):
+ '''Executes API calls for each previously computed metric descriptor.
+
+ Args:
+ project_id: monitoring project id where to write descriptors.
+ existing: map of existing descriptor types.
+ computed: list of plugins.MetricDescriptor instances previously computed.
+ '''
+ LOGGER.info('timeseries descriptors start')
+ requests = plugins.monitoring.descriptor_requests(project_id, MONITORING_ROOT,
+ existing, computed)
+ num = 0
+ for request in requests:
+ fetch(request)
+ num += 1
+ LOGGER.info('timeseries descriptors end (computed: {} created: {})'.format(
+ len(computed), num))
+
+
+def do_timeseries(project_id, timeseries, descriptors):
+ '''Executes API calls for each previously computed timeseries.
+
+ Args:
+ project_id: monitoring project id where to write timeseries.
+ timeseries: list of plugins.Timeseries instances.
+ descriptors: list of plugins.MetricDescriptor instances matching timeseries.
+ '''
+ LOGGER.info('timeseries start')
+ requests = plugins.monitoring.timeseries_requests(project_id, MONITORING_ROOT,
+ timeseries, descriptors)
+ num = 0
+ for request in requests:
+ fetch(request)
+ num += 1
+ LOGGER.info('timeseries end (number: {} requests: {})'.format(
+ len(timeseries), num))
+
+
+def fetch(request):
+ '''Minimal HTTP client interface for API calls.
+
+ Executes the HTTP request passed as argument using the google.auth
+ authenticated session.
+
+ Args:
+ request: an instance of plugins.HTTPRequest.
+ Returns:
+ JSON-decoded or raw response depending on the 'json' request attribute.
+ '''
+ # try
+ LOGGER.debug(f'fetch {"POST" if request.data else "GET"} {request.url}')
+ try:
+ if not request.data:
+ response = HTTP.get(request.url, headers=request.headers)
+ else:
+ response = HTTP.post(request.url, headers=request.headers,
+ data=request.data)
+ except google.auth.exceptions.RefreshError as e:
+ raise SystemExit(e.args[0])
+ if response.status_code != 200:
+ LOGGER.critical(
+ f'response code {response.status_code} for URL {request.url}')
+ LOGGER.critical(response.content)
+ print(request.data)
+ raise SystemExit(1)
+ return response
+
+
+def main_cf_pubsub(event, context):
+ 'Entry point for Cloud Function triggered by a PubSub message.'
+ debug = os.environ.get('DEBUG')
+ logging.basicConfig(level=logging.DEBUG if debug else logging.INFO)
+ LOGGER.info('processing pubsub payload')
+ try:
+ payload = json.loads(base64.b64decode(event['data']).decode('utf-8'))
+ except (binascii.Error, json.JSONDecodeError) as e:
+ raise SystemExit(f'Invalid payload: e.args[0].')
+ discovery_root = payload.get('discovery_root')
+ monitoring_project = payload.get('monitoring_project')
+ if not discovery_root:
+ LOGGER.critical('no discovery roo project specified')
+ LOGGER.info(payload)
+ raise SystemExit(f'Invalid options')
+ if not monitoring_project:
+ LOGGER.critical('no monitoring project specified')
+ LOGGER.info(payload)
+ raise SystemExit(f'Invalid options')
+ if discovery_root.partition('/')[0] not in ('folders', 'organizations'):
+ raise SystemExit(f'Invalid discovery root {discovery_root}.')
+ custom_quota = payload.get('custom_quota', {})
+ descriptors = []
+ folders = payload.get('folders', [])
+ projects = payload.get('projects', [])
+ resources = {}
+ timeseries = []
+ do_init(resources, discovery_root, monitoring_project, folders, projects,
+ custom_quota)
+ do_discovery(resources)
+ do_timeseries_calc(resources, descriptors, timeseries)
+ do_timeseries_descriptors(monitoring_project, resources['metric-descriptors'],
+ descriptors)
+ do_timeseries(monitoring_project, timeseries, descriptors)
+
+
+@click.command()
+@click.option(
+ '--discovery-root', '-dr', required=True,
+ help='Root node for asset discovery, organizations/nnn or folders/nnn.')
+@click.option('--monitoring-project', '-mon', required=True, type=str,
+ help='GCP monitoring project where metrics will be stored.')
+@click.option('--project', '-p', type=str, multiple=True,
+ help='GCP project id, can be specified multiple times.')
+@click.option('--folder', '-f', type=int, multiple=True,
+ help='GCP folder id, can be specified multiple times.')
+@click.option('--custom-quota-file', type=click.File('r'),
+ help='Custom quota file in yaml format.')
+@click.option('--dump-file', type=click.File('w'),
+ help='Export JSON representation of resources to file.')
+@click.option('--load-file', type=click.File('r'),
+ help='Load JSON resources from file, skips init and discovery.')
+@click.option('--debug-plugin',
+ help='Run only core and specified timeseries plugin.')
+def main(discovery_root, monitoring_project, project=None, folder=None,
+ custom_quota_file=None, dump_file=None, load_file=None,
+ debug_plugin=None):
+ 'CLI entry point.'
+ logging.basicConfig(level=logging.INFO)
+ if discovery_root.partition('/')[0] not in ('folders', 'organizations'):
+ raise SystemExit('Invalid discovery root.')
+ descriptors = []
+ timeseries = []
+ if load_file:
+ resources = json.load(load_file)
+ else:
+ custom_quota = {}
+ resources = {}
+ if custom_quota_file:
+ try:
+ custom_quota = yaml.load(custom_quota_file, Loader=yaml.Loader)
+ except yaml.YAMLError as e:
+ raise SystemExit(f'Error decoding custom quota file: {e.args[0]}')
+ do_init(resources, discovery_root, monitoring_project, folder, project,
+ custom_quota)
+ do_discovery(resources)
+ if dump_file:
+ json.dump(resources, dump_file, indent=2)
+ do_timeseries_calc(resources, descriptors, timeseries, debug_plugin)
+ do_timeseries_descriptors(monitoring_project, resources['metric-descriptors'],
+ descriptors)
+ do_timeseries(monitoring_project, timeseries, descriptors)
+
+
+if __name__ == '__main__':
+ main(auto_envvar_prefix='NETMON')
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/__init__.py b/blueprints/cloud-operations/network-dashboard/src/plugins/__init__.py
new file mode 100644
index 000000000..1bdc4cb20
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/__init__.py
@@ -0,0 +1,81 @@
+# 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.
+'''Plugin interface objects and registration functions.
+
+This module export the objects passed to and returned from plugin functions,
+and the function used to register plugins for each stage, and get all plugins
+for individual stages.
+'''
+
+import collections
+import enum
+import functools
+import importlib
+import pathlib
+import pkgutil
+import types
+
+__all__ = [
+ 'HTTPRequest', 'Level', 'PluginError', 'Resource', 'get_discovery_plugins',
+ 'get_init_plugins', 'register_discovery', 'register_init'
+]
+
+_PLUGINS_DISCOVERY = []
+_PLUGINS_INIT = []
+_PLUGINS_TIMESERIES = []
+
+HTTPRequest = collections.namedtuple('HTTPRequest', 'url headers data json',
+ defaults=[True])
+Level = enum.IntEnum('Level', 'CORE PRIMARY DERIVED')
+MetricDescriptor = collections.namedtuple('MetricDescriptor',
+ 'type name labels is_ratio',
+ defaults=[False])
+Plugin = collections.namedtuple('Plugin', 'func name level priority',
+ defaults=[Level.PRIMARY, 99])
+Resource = collections.namedtuple('Resource', 'type id data key',
+ defaults=[None])
+TimeSeries = collections.namedtuple('TimeSeries', 'metric value labels')
+
+
+class PluginError(Exception):
+ pass
+
+
+def _register_plugin(collection, *args):
+ 'Derive plugin name from function and add to its collection.'
+ if args and type(args[0]) == types.FunctionType:
+ collection.append(
+ Plugin(args[0], f'{args[0].__module__}.{args[0].__name__}'))
+ return
+
+ def outer(func):
+ collection.append(Plugin(func, f'{func.__module__}.{func.__name__}', *args))
+ return func
+
+ return outer
+
+
+get_discovery_plugins = functools.partial(iter, _PLUGINS_DISCOVERY)
+get_init_plugins = functools.partial(iter, _PLUGINS_INIT)
+get_timeseries_plugins = functools.partial(iter, _PLUGINS_TIMESERIES)
+register_discovery = functools.partial(_register_plugin, _PLUGINS_DISCOVERY)
+register_init = functools.partial(_register_plugin, _PLUGINS_INIT)
+register_timeseries = functools.partial(_register_plugin, _PLUGINS_TIMESERIES)
+
+_plugins_path = str(pathlib.Path(__file__).parent)
+
+for mod_info in pkgutil.iter_modules([_plugins_path], 'plugins.'):
+ importlib.import_module(mod_info.name)
+
+_PLUGINS_DISCOVERY.sort(key=lambda i: i.level)
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/core-discover-cai-nodes.py b/blueprints/cloud-operations/network-dashboard/src/plugins/core-discover-cai-nodes.py
new file mode 100644
index 000000000..dc5c53247
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/core-discover-cai-nodes.py
@@ -0,0 +1,80 @@
+# 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.
+'''Project and folder discovery from configuration options.
+
+This plugin needs to run first, as it's responsible for discovering nodes that
+contain resources: folders and projects contained in the hierarchy passed in
+via configuration options. Node resources are fetched from Cloud Asset
+Inventory based on explicit id or being part of a folder hierarchy.
+'''
+
+import logging
+
+from . import HTTPRequest, Level, Resource, register_init, register_discovery
+from .utils import parse_page_token, parse_cai_results
+
+LOGGER = logging.getLogger('net-dash.discovery.cai-nodes')
+
+CAI_URL = ('https://content-cloudasset.googleapis.com/v1p1beta1'
+ '/{}/resources:searchAll'
+ '?assetTypes=cloudresourcemanager.googleapis.com/Folder'
+ '&assetTypes=cloudresourcemanager.googleapis.com/Project'
+ '&pageSize=500')
+
+
+def _handle_discovery(resources, response, data):
+ 'Processes asset response and returns project resources or next URLs.'
+ LOGGER.info('discovery handle request')
+ for result in parse_cai_results(data, 'nodes'):
+ asset_type = result['assetType'].split('/')[-1]
+ name = result['name'].split('/')[-1]
+ if asset_type == 'Folder':
+ yield Resource('folders', name, {'name': result['displayName']})
+ elif asset_type == 'Project':
+ number = result['project'].split('/')[1]
+ data = {'number': number, 'project_id': name}
+ yield Resource('projects', name, data)
+ yield Resource('projects:number', number, data)
+ else:
+ LOGGER.info(f'unknown resource {name}')
+ next_url = parse_page_token(data, response.request.url)
+ if next_url:
+ LOGGER.info('discovery next url')
+ yield HTTPRequest(next_url, {}, None)
+
+
+@register_init
+def init(resources):
+ 'Prepares project datastructures in the shared resource map.'
+ LOGGER.info('init')
+ resources.setdefault('folders', {})
+ resources.setdefault('projects', {})
+ resources.setdefault('projects:number', {})
+
+
+@register_discovery(Level.CORE, 0)
+def start_discovery(resources, response=None, data=None):
+ 'Plugin entry point, triggers discovery and handles requests and responses.'
+ LOGGER.info(f'discovery (has response: {response is not None})')
+ if response is None:
+ # return initial discovery URLs
+ for v in resources['config:folders']:
+ yield HTTPRequest(CAI_URL.format(f'folders/{v}'), {}, None)
+ for v in resources['config:projects']:
+ if v not in resources['projects']:
+ yield HTTPRequest(CAI_URL.format(f'projects/{v}'), {}, None)
+ else:
+ # pass the API response to the plugin data handler and return results
+ for result in _handle_discovery(resources, response, data):
+ yield result
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py
new file mode 100644
index 000000000..1ac62d066
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py
@@ -0,0 +1,301 @@
+# 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.
+'''Compute resources discovery from Cloud Asset Inventory.
+
+This plugin handles discovery for Compute resources via a broad org-level
+scoped CAI search. Common resource attributes are parsed by a generic handler
+function, which then delegates parsing of resource-level attributes to smaller
+specialized functions, one per resource type.
+'''
+
+import logging
+
+from . import HTTPRequest, Level, Resource, register_init, register_discovery
+from .utils import parse_cai_results
+
+CAI_URL = ('https://content-cloudasset.googleapis.com/v1'
+ '/{root}/assets'
+ '?contentType=RESOURCE&{asset_types}&pageSize=500')
+LOGGER = logging.getLogger('net-dash.discovery.cai-compute')
+TYPES = {
+ 'addresses': 'compute.googleapis.com/Address',
+ 'global_addresses': 'compute.googleapis.com/GlobalAddress',
+ 'firewall_policies': 'compute.googleapis.com/FirewallPolicy',
+ 'firewall_rules': 'compute.googleapis.com/Firewall',
+ 'forwarding_rules': 'compute.googleapis.com/ForwardingRule',
+ 'instances': 'compute.googleapis.com/Instance',
+ 'networks': 'compute.googleapis.com/Network',
+ 'subnetworks': 'compute.googleapis.com/Subnetwork',
+ 'routers': 'compute.googleapis.com/Router',
+ 'routes': 'compute.googleapis.com/Route',
+ 'sql_instances': 'sqladmin.googleapis.com/Instance',
+ 'filestore_instances': 'file.googleapis.com/Instance',
+ 'memorystore_instances': 'redis.googleapis.com/Instance',
+}
+NAMES = {v: k for k, v in TYPES.items()}
+
+
+def _get_parent(parent, resources):
+ 'Extracts and returns resource parent and type.'
+ parent_type, parent_id = parent.split('/')[-2:]
+ if parent_type == 'projects':
+ project = resources['projects:number'].get(parent_id)
+ if project:
+ return {'project_id': project['project_id'], 'project_number': parent_id}
+ if parent_type == 'folders':
+ if parent_id in resources['folders']:
+ return {'parent': f'{parent_type}/{parent_id}'}
+ if resources.get('organization') == parent_id:
+ return {'parent': f'{parent_type}/{parent_id}'}
+
+
+def _handle_discovery(resources, response, data):
+ 'Processes the asset API response and returns parsed resources or next URL.'
+ LOGGER.info('discovery handle request')
+ for result in parse_cai_results(data, 'cai-compute', method='list'):
+ resource = _handle_resource(resources, result['assetType'],
+ result['resource'])
+ if not resource:
+ continue
+ yield resource
+ page_token = data.get('nextPageToken')
+ if page_token:
+ LOGGER.info('requesting next page')
+ url = _url(resources)
+ yield HTTPRequest(f'{url}&pageToken={page_token}', {}, None)
+
+
+def _handle_resource(resources, asset_type, data):
+ 'Parses and returns a single resource. Calls resource-level handler.'
+ # general attributes shared by all resource types
+ attrs = data['data']
+ # we use the asset type as the discovery name sometimes does not match
+ # e.g. assetType = GlobalAddress but discoveryName = Address
+ resource_name = NAMES[asset_type]
+ resource = {
+ 'id':
+ attrs.get('id'),
+ 'name':
+ attrs['name'],
+ # Some resources (ex: Filestore) don't have a self_link, using parent + name in that case
+ 'self_link':
+ f'{data["parent"]}/{attrs["name"]}'
+ if not 'selfLink' in attrs else _self_link(attrs['selfLink']),
+ 'assetType':
+ asset_type
+ }
+ # derive parent type and id and skip if parent is not within scope
+ parent_data = _get_parent(data['parent'], resources)
+ if not parent_data:
+ LOGGER.info(f'{resource["self_link"]} outside perimeter')
+ LOGGER.debug([
+ resources['organization'], resources['folders'],
+ resources['projects:number']
+ ])
+ return
+ resource.update(parent_data)
+ # gets and calls the resource-level handler for type specific attributes
+ func = globals().get(f'_handle_{resource_name}')
+ if not callable(func):
+ raise SystemExit(f'specialized function missing for {resource_name}')
+ extra_attrs = func(resource, attrs)
+ if not extra_attrs:
+ return
+ resource.update(extra_attrs)
+ return Resource(resource_name, resource['self_link'], resource)
+
+
+def _handle_addresses(resource, data):
+ 'Handles address type resource data.'
+ network = data.get('network')
+ subnet = data.get('subnetwork')
+ return {
+ 'address': data['address'],
+ 'internal': data.get('addressType') == 'INTERNAL',
+ 'purpose': data.get('purpose', ''),
+ 'status': data.get('status', ''),
+ 'network': None if not network else _self_link(network),
+ 'subnetwork': None if not subnet else _self_link(subnet)
+ }
+
+
+def _handle_firewall_policies(resource, data):
+ 'Handles firewall policy type resource data.'
+ return {
+ 'num_rules': len(data.get('rules', [])),
+ 'num_tuples': data.get('ruleTupleCount', 0)
+ }
+
+
+def _handle_firewall_rules(resource, data):
+ 'Handles firewall type resource data.'
+ return {'network': _self_link(data['network'])}
+
+
+def _handle_forwarding_rules(resource, data):
+ 'Handles forwarding_rules type resource data.'
+ network = data.get('network')
+ region = data.get('region')
+ subnet = data.get('subnetwork')
+ return {
+ 'address': data.get('IPAddress'),
+ 'load_balancing_scheme': data.get('loadBalancingScheme', ''),
+ 'network': None if not network else _self_link(network),
+ 'psc_accepted': data.get('pscConnectionStatus') == 'ACCEPTED',
+ 'region': None if not region else region.split('/')[-1],
+ 'subnetwork': None if not subnet else _self_link(subnet)
+ }
+
+
+def _handle_global_addresses(resource, data):
+ 'Handles GlobalAddress type resource data (ex: PSA ranges).'
+ network = data.get('network')
+ return {
+ 'address': data['address'],
+ 'prefixLength': data.get('prefixLength') or None,
+ 'internal': data.get('addressType') == 'INTERNAL',
+ 'purpose': data.get('purpose', ''),
+ 'status': data.get('status', ''),
+ 'network': None if not network else _self_link(network),
+ }
+
+
+def _handle_instances(resource, data):
+ 'Handles instance type resource data.'
+ if data['status'] != 'RUNNING':
+ return
+ networks = [{
+ 'network': _self_link(i['network']),
+ 'subnetwork': _self_link(i['subnetwork'])
+ } for i in data.get('networkInterfaces', [])]
+ return {'zone': data['zone'], 'networks': networks}
+
+
+def _handle_networks(resource, data):
+ 'Handles network type resource data.'
+ peerings = [{
+ 'active': p['state'] == 'ACTIVE',
+ 'name': p['name'],
+ 'network': _self_link(p['network']),
+ 'project_id': _self_link(p['network']).split('/')[1]
+ } for p in data.get('peerings', [])]
+ subnets = [_self_link(s) for s in data.get('subnetworks', [])]
+ return {'peerings': peerings, 'subnetworks': subnets}
+
+
+def _handle_routers(resource, data):
+ 'Handles router type resource data.'
+ return {
+ 'network': _self_link(data['network']),
+ 'region': data['region'].split('/')[-1]
+ }
+
+
+def _handle_routes(resource, data):
+ 'Handles route type resource data.'
+ hop = [
+ a.removeprefix('nextHop').lower() for a in data if a.startswith('nextHop')
+ ]
+ return {'next_hop_type': hop[0], 'network': _self_link(data['network'])}
+
+
+def _handle_sql_instances(resource, data):
+ 'Handles cloud sql instance type resource data.'
+ return {
+ 'name': data['name'],
+ 'self_link': _self_link(data['selfLink']),
+ 'ipAddresses': [
+ i['ipAddress'] for i in data['ipAddresses'] if i['type'] == 'PRIVATE'
+ ],
+ 'region': data['region'],
+ 'availabilityType': data['settings']['availabilityType'],
+ 'network': data['settings']['ipConfiguration']['privateNetwork']
+ }
+
+
+def _handle_filestore_instances(resource, data):
+ 'Handles filestore instance type resource data.'
+ return {
+ # Getting only the instance name, removing the rest
+ 'name': data['name'].split('/')[-1],
+ # Is a list but for now, only one network is supported for Filestore
+ 'network': data['networks'][0]['network'],
+ 'reservedIpRange': data['networks'][0]['reservedIpRange'],
+ 'ipAddresses': data['networks'][0]['ipAddresses']
+ }
+
+
+def _handle_memorystore_instances(resource, data):
+ 'Handles Memorystore (Redis) instance type resource data.'
+ return {
+ # Getting only the instance name, removing the rest
+ 'name':
+ data['name'].split('/')[-1],
+ 'locationId':
+ data['locationId'],
+ 'replicaCount':
+ 0 if not 'replicaCount' in data else data['replicaCount'],
+ 'network':
+ data['authorizedNetwork'],
+ 'reservedIpRange':
+ '' if not 'reservedIpRange' in data else data['reservedIpRange'],
+ 'host':
+ '' if not 'host' in data else data['host'],
+ }
+
+
+def _handle_subnetworks(resource, data):
+ 'Handles subnetwork type resource data.'
+ secondary_ranges = [{
+ 'name': s['rangeName'],
+ 'cidr_range': s['ipCidrRange']
+ } for s in data.get('secondaryIpRanges', [])]
+ return {
+ 'cidr_range': data['ipCidrRange'],
+ 'network': _self_link(data['network']),
+ 'purpose': data.get('purpose'),
+ 'region': data['region'].split('/')[-1],
+ 'secondary_ranges': secondary_ranges
+ }
+
+
+def _self_link(s):
+ 'Removes initial part from self links.'
+ return '/'.join(s.split('/')[5:])
+
+
+def _url(resources):
+ 'Returns discovery URL'
+ discovery_root = resources['config:discovery_root']
+ asset_types = '&'.join(f'assetTypes={t}' for t in TYPES.values())
+ return CAI_URL.format(root=discovery_root, asset_types=asset_types)
+
+
+@register_init
+def init(resources):
+ 'Prepares the datastructures for types managed here in the resource map.'
+ LOGGER.info('init')
+ for name in TYPES:
+ resources.setdefault(name, {})
+
+
+@register_discovery(Level.PRIMARY, 10)
+def start_discovery(resources, response=None, data=None):
+ 'Plugin entry point, triggers discovery and handles requests and responses.'
+ LOGGER.info(f'discovery (has response: {response is not None})')
+ if response is None:
+ yield HTTPRequest(_url(resources), {}, None)
+ else:
+ for result in _handle_discovery(resources, response, data):
+ yield result
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/discover-compute-quota.py b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-compute-quota.py
new file mode 100644
index 000000000..9c9e8f948
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-compute-quota.py
@@ -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.
+'''Discovers project quota via Compute API and overlay user overrides.
+
+This plugin discovers project quota via batch Compute API requests. Project and
+network quotas are then optionally overlaid with custom quota modifiers passed
+in as options. Region quota discovery is partially implemented but not active.
+'''
+
+import logging
+
+from . import Level, Resource, register_init, register_discovery
+from .utils import batched, poor_man_mp_request, poor_man_mp_response
+
+LOGGER = logging.getLogger('net-dash.discovery.compute-quota')
+NAME = 'quota'
+
+API_GLOBAL_URL = '/compute/v1/projects/{}'
+API_REGION_URL = '/compute/v1/projects/{}/regions/{}'
+
+
+def _handle_discovery(resources, response):
+ 'Processes asset batch response and overlays custom quota.'
+ LOGGER.info('discovery handle request')
+ content_type = response.headers['content-type']
+ per_project_quota = resources['config:custom_quota'].get('projects', {})
+ # process batch response
+ for part in poor_man_mp_response(content_type, response.content):
+ kind = part.get('kind')
+ quota = {
+ q['metric']: int(q['limit'])
+ for q in sorted(part.get('quotas', []), key=lambda v: v['metric'])
+ }
+ self_link = part.get('selfLink')
+ if not self_link:
+ logging.warn('invalid quota response')
+ self_link = self_link.split('/')
+ if kind == 'compute#project':
+ project_id = self_link[-1]
+ region = 'global'
+ elif kind == 'compute#region':
+ project_id = self_link[-3]
+ region = self_link[-1]
+ # custom quota overrides
+ for k, v in per_project_quota.get(project_id, {}).get(region, {}).items():
+ quota[k] = int(v)
+ if project_id not in resources[NAME]:
+ resources[NAME][project_id] = {}
+ yield Resource(NAME, project_id, quota, region)
+
+
+@register_init
+def init(resources):
+ 'Prepares quota datastructures in the shared resource map.'
+ LOGGER.info('init')
+ resources.setdefault(NAME, {})
+
+
+@register_discovery(Level.DERIVED, 0)
+def start_discovery(resources, response=None):
+ 'Plugin entry point, triggers discovery and handles requests and responses.'
+ LOGGER.info(f'discovery (has response: {response is not None})')
+ if response is None:
+ # TODO: regions
+ urls = [API_GLOBAL_URL.format(p) for p in resources['projects']]
+ if not urls:
+ return
+ for batch in batched(urls, 10):
+ yield poor_man_mp_request(batch)
+ else:
+ for result in _handle_discovery(resources, response):
+ yield result
+ # store custom network-level quota
+ per_network_quota = resources['config:custom_quota'].get('networks', {})
+ for network_id, overrides in per_network_quota.items():
+ quota = {k: int(v) for k, v in overrides.items()}
+ yield Resource(NAME, network_id, quota)
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/discover-compute-routerstatus.py b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-compute-routerstatus.py
new file mode 100644
index 000000000..cd2840b77
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-compute-routerstatus.py
@@ -0,0 +1,89 @@
+# 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.
+'''Discovers dynamic route counts via router status.
+
+This plugin depends on the CAI Compute one as it discovers dynamic route
+data by parsing router status, and it needs routers to have already been
+discovered. It uses batch Compute API requests via the utils functions.
+'''
+
+import logging
+
+from . import Level, Resource, register_init, register_discovery
+from .utils import batched, poor_man_mp_request, poor_man_mp_response
+
+LOGGER = logging.getLogger('net-dash.discovery.compute-routes-dynamic')
+NAME = 'routes_dynamic'
+
+API_URL = '/compute/v1/projects/{}/regions/{}/routers/{}/getRouterStatus'
+
+
+def _handle_discovery(resources, response):
+ 'Processes asset batch response and parses router status data.'
+ LOGGER.info('discovery handle request')
+ content_type = response.headers['content-type']
+ routers = [r for r in resources['routers'].values()]
+ # process batch response
+ for i, part in enumerate(poor_man_mp_response(content_type,
+ response.content)):
+ router = routers[i]
+ result = part.get('result')
+ if not result:
+ LOGGER.info(f'skipping router {router["self_link"]}, no result')
+ continue
+ bgp_peer_status = result.get('bgpPeerStatus')
+ if not bgp_peer_status:
+ LOGGER.info(f'skipping router {router["self_link"]}, no bgp peer status')
+ continue
+ network = result.get('network')
+ if not network:
+ LOGGER.info(f'skipping router {router["self_link"]}, no bgp peer status')
+ continue
+ if not network.endswith(router['network']):
+ LOGGER.warn(
+ f'router network mismatch: got {network} expected {router["network"]}'
+ )
+ continue
+ num_learned_routes = sum(
+ int(p.get('numLearnedRoutes', 0)) for p in bgp_peer_status)
+ if router['network'] not in resources[NAME]:
+ resources[NAME][router['network']] = {}
+ yield Resource(NAME, router['network'], num_learned_routes,
+ router['self_link'])
+ yield
+
+
+@register_init
+def init(resources):
+ 'Prepares dynamic routes datastructure in the shared resource map.'
+ LOGGER.info('init')
+ resources.setdefault(NAME, {})
+
+
+@register_discovery(Level.DERIVED)
+def start_discovery(resources, response=None):
+ 'Plugin entry point, triggers discovery and handles requests and responses.'
+ LOGGER.info(f'discovery (has response: {response is not None})')
+ if not response:
+ urls = [
+ API_URL.format(r['project_id'], r['region'], r['name'])
+ for r in resources['routers'].values()
+ ]
+ if not urls:
+ return
+ for batch in batched(urls, 10):
+ yield poor_man_mp_request(batch)
+ else:
+ for result in _handle_discovery(resources, response):
+ yield result
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/discover-group-networks.py b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-group-networks.py
new file mode 100644
index 000000000..350c288b4
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-group-networks.py
@@ -0,0 +1,39 @@
+# 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.
+'Group discovered networks by project.'
+
+import itertools
+import logging
+
+from . import Level, Resource, register_init, register_discovery
+
+LOGGER = logging.getLogger('net-dash.discovery.compute-routes-dynamic')
+NAME = 'networks:project'
+
+
+@register_init
+def init(resources):
+ 'Prepares datastructure in the shared resource map.'
+ LOGGER.info('init')
+ resources.setdefault(NAME, {})
+
+
+@register_discovery(Level.DERIVED)
+def start_discovery(resources, response=None):
+ 'Plugin entry point, group and return discovered networks.'
+ LOGGER.info(f'discovery (has response: {response is not None})')
+ grouped = itertools.groupby(resources['networks'].values(),
+ lambda v: v['project_id'])
+ for project_id, vpcs in grouped:
+ yield Resource(NAME, project_id, [v['self_link'] for v in vpcs])
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/discover-metric-descriptors.py b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-metric-descriptors.py
new file mode 100644
index 000000000..a9e4090de
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-metric-descriptors.py
@@ -0,0 +1,69 @@
+# 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.
+'''Discover existing network dashboard metric descriptors.
+
+Populating this data allows the tool to later compute which metric descriptors
+need to be created.
+'''
+
+import logging
+import urllib.parse
+
+from . import HTTPRequest, Level, Resource, register_init, register_discovery
+from .utils import parse_page_token
+
+LOGGER = logging.getLogger('net-dash.discovery.metrics')
+NAME = 'metric-descriptors'
+
+URL = ('https://content-monitoring.googleapis.com/v3/projects'
+ '/{}/metricDescriptors'
+ '?filter=metric.type%3Dstarts_with(%22custom.googleapis.com%2F{}%22)'
+ '&pageSize=500')
+
+
+def _handle_discovery(resources, response, data):
+ 'Processes monitoring API response and parses descriptor data.'
+ LOGGER.info('discovery handle request')
+ descriptors = data.get('metricDescriptors')
+ if not descriptors:
+ LOGGER.info('no descriptors found')
+ return
+ for d in descriptors:
+ yield Resource(NAME, d['type'], {})
+ next_url = parse_page_token(data, response.request.url)
+ if next_url:
+ LOGGER.info('discovery next url')
+ yield HTTPRequest(next_url, {}, None)
+
+
+@register_init
+def init(resources):
+ 'Prepares datastructure in the shared resource map.'
+ LOGGER.info('init')
+ resources.setdefault(NAME, {})
+
+
+@register_discovery(Level.CORE, 99)
+def start_discovery(resources, response=None, data=None):
+ 'Plugin entry point, triggers discovery and handles requests and responses.'
+ LOGGER.info(f'discovery (has response: {response is not None})')
+ project_id = resources['config:monitoring_project']
+ type_root = resources['config:monitoring_root']
+ url = URL.format(urllib.parse.quote_plus(project_id),
+ urllib.parse.quote_plus(type_root))
+ if response is None:
+ yield HTTPRequest(url, {}, None)
+ else:
+ for result in _handle_discovery(resources, response, data):
+ yield result
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/monitoring.py b/blueprints/cloud-operations/network-dashboard/src/plugins/monitoring.py
new file mode 100644
index 000000000..de4eae897
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/monitoring.py
@@ -0,0 +1,106 @@
+# 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.
+'Utility functions to create monitoring API requests.'
+
+import collections
+import datetime
+import json
+import logging
+
+from . import HTTPRequest
+from .utils import batched
+
+DESCRIPTOR_TYPE_BASE = 'custom.googleapis.com/{}'
+DESCRIPTOR_URL = ('https://content-monitoring.googleapis.com/v3'
+ '/projects/{}/metricDescriptors?alt=json')
+HEADERS = {'content-type': 'application/json'}
+LOGGER = logging.getLogger('net-dash.plugins.monitoring')
+TIMESERIES_URL = ('https://content-monitoring.googleapis.com/v3'
+ '/projects/{}/timeSeries?alt=json')
+
+
+def descriptor_requests(project_id, root, existing, computed):
+ 'Returns create requests for missing descriptors.'
+ type_base = DESCRIPTOR_TYPE_BASE.format(root)
+ url = DESCRIPTOR_URL.format(project_id)
+ for descriptor in computed:
+ d_type = f'{type_base}{descriptor.type}'
+ if d_type in existing:
+ continue
+ LOGGER.info(f'creating descriptor {d_type}')
+ if descriptor.is_ratio:
+ unit = '10^2.%'
+ value_type = 'DOUBLE'
+ else:
+ unit = '1'
+ value_type = 'INT64'
+ data = json.dumps({
+ 'type': d_type,
+ 'displayName': descriptor.name,
+ 'metricKind': 'GAUGE',
+ 'valueType': value_type,
+ 'unit': unit,
+ 'monitoredResourceTypes': ['global'],
+ 'labels': [{
+ 'key': l,
+ 'valueType': 'STRING'
+ } for l in descriptor.labels]
+ })
+ yield HTTPRequest(url, HEADERS, data)
+
+
+def timeseries_requests(project_id, root, timeseries, descriptors):
+ 'Returns create requests for timeseries.'
+ descriptor_valuetypes = {d.type: d.is_ratio for d in descriptors}
+ end_time = ''.join((datetime.datetime.utcnow().isoformat('T'), 'Z'))
+ type_base = DESCRIPTOR_TYPE_BASE.format(root)
+ url = TIMESERIES_URL.format(project_id)
+ # group timeseries in buckets by their type so that multiple timeseries
+ # can be grouped in a single API request without grouping duplicates types
+ ts_buckets = {}
+ for ts in timeseries:
+ bucket = ts_buckets.setdefault(ts.metric, collections.deque())
+ bucket.append(ts)
+ LOGGER.info(f'metric types {list(ts_buckets.keys())}')
+ ts_buckets = list(ts_buckets.values())
+ while ts_buckets:
+ data = {'timeSeries': []}
+ for bucket in ts_buckets:
+ ts = bucket.popleft()
+ if descriptor_valuetypes[ts.metric]:
+ pv = 'doubleValue'
+ else:
+ pv = 'int64Value'
+ data['timeSeries'].append({
+ 'metric': {
+ 'type': f'{type_base}{ts.metric}',
+ 'labels': ts.labels
+ },
+ 'resource': {
+ 'type': 'global'
+ },
+ 'points': [{
+ 'interval': {
+ 'endTime': end_time
+ },
+ 'value': {
+ pv: ts.value
+ }
+ }]
+ })
+ req_num = len(data['timeSeries'])
+ tot_num = sum(len(b) for b in ts_buckets)
+ LOGGER.info(f'sending {req_num} remaining: {tot_num}')
+ yield HTTPRequest(url, HEADERS, json.dumps(data))
+ ts_buckets = [b for b in ts_buckets if b]
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/series-firewall-policies.py b/blueprints/cloud-operations/network-dashboard/src/plugins/series-firewall-policies.py
new file mode 100644
index 000000000..defd69753
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/series-firewall-policies.py
@@ -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.
+'Prepares descriptors and timeseries for firewall policy resources.'
+
+import logging
+
+from . import MetricDescriptor, TimeSeries, register_timeseries
+
+DESCRIPTOR_ATTRS = {
+ 'tuples_used': 'Firewall tuples used per policy',
+ 'tuples_available': 'Firewall tuples limit per policy',
+ 'tuples_used_ratio': 'Firewall tuples used ratio per policy'
+}
+DESCRIPTOR_LABELS = ('parent', 'name')
+LOGGER = logging.getLogger('net-dash.timeseries.firewall-policies')
+TUPLE_LIMIT = 2000
+
+
+@register_timeseries
+def timeseries(resources):
+ 'Returns used/available/ratio firewall tuples timeseries by policy.'
+ LOGGER.info('timeseries')
+ for dtype, name in DESCRIPTOR_ATTRS.items():
+ yield MetricDescriptor(f'firewall_policy/{dtype}', name, DESCRIPTOR_LABELS,
+ dtype.endswith('ratio'))
+ for v in resources['firewall_policies'].values():
+ tuples = int(v['num_tuples'])
+ labels = {'parent': v['parent'], 'name': v['name']}
+ yield TimeSeries('firewall_policy/tuples_used', tuples, labels)
+ yield TimeSeries('firewall_policy/tuples_available', TUPLE_LIMIT, labels)
+ yield TimeSeries('firewall_policy/tuples_used_ratio', tuples / TUPLE_LIMIT,
+ labels)
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/series-firewall-rules.py b/blueprints/cloud-operations/network-dashboard/src/plugins/series-firewall-rules.py
new file mode 100644
index 000000000..5490e6d3b
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/series-firewall-rules.py
@@ -0,0 +1,59 @@
+# 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.
+'Prepares descriptors and timeseries for firewall rules by project and network.'
+
+import itertools
+import logging
+
+from . import MetricDescriptor, TimeSeries, register_timeseries
+
+DESCRIPTOR_ATTRS = {
+ 'firewall_rules_used': 'Firewall rules used per project',
+ 'firewall_rules_available': 'Firewall rules limit per project',
+ 'firewall_rules_used_ratio': 'Firewall rules used ratio per project',
+}
+LOGGER = logging.getLogger('net-dash.timeseries.firewall-rules')
+
+
+@register_timeseries
+def timeseries(resources):
+ 'Returns used/available/ratio firewall timeseries by project and network.'
+ LOGGER.info('timeseries')
+ # return a single descriptor for network as we don't have limits
+ yield MetricDescriptor(f'network/firewall_rules_used',
+ 'Firewall rules used per network', ('project', 'name'))
+ # return used/vailable/ratio descriptors for project
+ for dtype, name in DESCRIPTOR_ATTRS.items():
+ yield MetricDescriptor(f'project/{dtype}', name, ('project',),
+ dtype.endswith('ratio'))
+ # group firewall rules by network then prepare and return timeseries
+ grouped = itertools.groupby(resources['firewall_rules'].values(),
+ lambda v: v['network'])
+ for network_id, rules in grouped:
+ count = len(list(rules))
+ labels = {
+ 'name': resources['networks'][network_id]['name'],
+ 'project': resources['networks'][network_id]['project_id']
+ }
+ yield TimeSeries('network/firewall_rules_used', count, labels)
+ # group firewall rules by project then prepare and return timeseries
+ grouped = itertools.groupby(resources['firewall_rules'].values(),
+ lambda v: v['project_id'])
+ for project_id, rules in grouped:
+ count = len(list(rules))
+ limit = int(resources['quota'][project_id]['global']['FIREWALLS'])
+ labels = {'project': project_id}
+ yield TimeSeries('project/firewall_rules_used', count, labels)
+ yield TimeSeries('project/firewall_rules_available', limit, labels)
+ yield TimeSeries('project/firewall_rules_used_ratio', count / limit, labels)
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/series-networks.py b/blueprints/cloud-operations/network-dashboard/src/plugins/series-networks.py
new file mode 100644
index 000000000..0ce7a4b30
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/series-networks.py
@@ -0,0 +1,142 @@
+# 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.
+'''Prepares descriptors and timeseries for network-level metrics.
+
+This plugin computes metrics for a variety of network resource types like
+subnets, instances, peerings, etc. It mostly does so by first grouping
+resources for a type, and then using a generalized function to derive counts
+and ratios and compute the actual timeseries.
+'''
+
+import functools
+import itertools
+import logging
+
+from . import MetricDescriptor, TimeSeries, register_timeseries
+
+DESCRIPTOR_ATTRS = {
+ 'forwarding_rules_l4_available': 'L4 fwd rules limit per network',
+ 'forwarding_rules_l4_used': 'L4 fwd rules used per network',
+ 'forwarding_rules_l4_used_ratio': 'L4 fwd rules used ratio per network',
+ 'forwarding_rules_l7_available': 'L7 fwd rules limit per network',
+ 'forwarding_rules_l7_used': 'L7 fwd rules used per network',
+ 'forwarding_rules_l7_used_ratio': 'L7 fwd rules used ratio per network',
+ 'instances_available': 'Instance limit per network',
+ 'instances_used': 'Instance used per network',
+ 'instances_used_ratio': 'Instance used ratio per network',
+ 'peerings_active_available': 'Active peering limit per network',
+ 'peerings_active_used': 'Active peering used per network',
+ 'peerings_active_used_ratio': 'Active peering used ratio per network',
+ 'peerings_total_available': 'Total peering limit per network',
+ 'peerings_total_used': 'Total peering used per network',
+ 'peerings_total_used_ratio': 'Total peering used ratio per network',
+ 'subnets_available': 'Subnet limit per network',
+ 'subnets_used': 'Subnet used per network',
+ 'subnets_used_ratio': 'Subnet used ratio per network'
+}
+LIMITS = {
+ 'INSTANCES_PER_NETWORK_GLOBAL': 15000,
+ 'INTERNAL_FORWARDING_RULES_PER_NETWORK': 500,
+ 'INTERNAL_MANAGED_FORWARDING_RULES_PER_NETWORK': 75,
+ 'ROUTES': 250,
+ 'SUBNET_RANGES_PER_NETWORK': 300
+}
+LOGGER = logging.getLogger('net-dash.timeseries.networks')
+
+
+def _group_timeseries(name, resources, grouped, limit_name):
+ 'Generalized function that returns timeseries from data grouped by network.'
+ for network_id, elements in grouped:
+ network = resources['networks'].get(network_id)
+ if not network:
+ LOGGER.info(f'out of scope {name} network {network_id}')
+ continue
+ count = len(list(elements))
+ labels = {'project': network['project_id'], 'network': network['name']}
+ quota = resources['quota'][network['project_id']]['global']
+ limit = quota.get(limit_name, LIMITS[limit_name])
+ yield TimeSeries(f'network/{name}_used', count, labels)
+ yield TimeSeries(f'network/{name}_available', limit, labels)
+ yield TimeSeries(f'network/{name}_used_ratio', count / limit, labels)
+
+
+def _forwarding_rules(resources):
+ 'Groups forwarding rules by network/type and returns relevant timeseries.'
+ # create two separate iterators filtered by L4 and L7 balancing schemes
+ filter = lambda n, v: v['load_balancing_scheme'] != n
+ forwarding_rules = resources['forwarding_rules'].values()
+ forwarding_rules_l4 = itertools.filterfalse(
+ functools.partial(filter, 'INTERNAL'), forwarding_rules)
+ forwarding_rules_l7 = itertools.filterfalse(
+ functools.partial(filter, 'INTERNAL_MANAGED'), forwarding_rules)
+ # group each iterator by network and return timeseries
+ grouped_l4 = itertools.groupby(forwarding_rules_l4, lambda i: i['network'])
+ grouped_l7 = itertools.groupby(forwarding_rules_l7, lambda i: i['network'])
+ return itertools.chain(
+ _group_timeseries('forwarding_rules_l4', resources, grouped_l4,
+ 'INTERNAL_FORWARDING_RULES_PER_NETWORK'),
+ _group_timeseries('forwarding_rules_l7', resources, grouped_l7,
+ 'INTERNAL_MANAGED_FORWARDING_RULES_PER_NETWORK'),
+ )
+
+
+def _instances(resources):
+ 'Groups instances by network and returns relevant timeseries.'
+ instance_networks = itertools.chain.from_iterable(
+ i['networks'] for i in resources['instances'].values())
+ grouped = itertools.groupby(instance_networks, lambda i: i['network'])
+ return _group_timeseries('instances', resources, grouped,
+ 'INSTANCES_PER_NETWORK_GLOBAL')
+
+
+def _peerings(resources):
+ 'Counts peerings by network and returns relevant timeseries.'
+ quota = resources['quota']
+ for network_id, network in resources['networks'].items():
+ labels = {'project': network['project_id'], 'network': network['name']}
+ limit = quota.get(network_id, {}).get('PEERINGS_PER_NETWORK', 250)
+ p_active = len([p for p in network['peerings'] if p['active']])
+ p_total = len(network['peerings'])
+ yield TimeSeries('network/peerings_active_used', p_active, labels)
+ yield TimeSeries('network/peerings_active_available', limit, labels)
+ yield TimeSeries('network/peerings_active_used_ratio', p_active / limit,
+ labels)
+ yield TimeSeries('network/peerings_total_used', p_total, labels)
+ yield TimeSeries('network/peerings_total_available', limit, labels)
+ yield TimeSeries('network/peerings_total_used_ratio', p_total / limit,
+ labels)
+
+
+def _subnet_ranges(resources):
+ 'Groups subnetworks by network and returns relevant timeseries.'
+ grouped = itertools.groupby(resources['subnetworks'].values(),
+ lambda v: v['network'])
+ return _group_timeseries('subnets', resources, grouped,
+ 'SUBNET_RANGES_PER_NETWORK')
+
+
+@register_timeseries
+def timeseries(resources):
+ 'Returns used/available/ratio timeseries by network for different resources.'
+ LOGGER.info('timeseries')
+ # return descriptors
+ for dtype, name in DESCRIPTOR_ATTRS.items():
+ yield MetricDescriptor(f'network/{dtype}', name, ('project', 'network'),
+ dtype.endswith('ratio'))
+
+ # chain iterators from specialized functions and yield combined timeseries
+ results = itertools.chain(_forwarding_rules(resources), _instances(resources),
+ _peerings(resources), _subnet_ranges(resources))
+ for result in results:
+ yield result
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/series-peering-groups.py b/blueprints/cloud-operations/network-dashboard/src/plugins/series-peering-groups.py
new file mode 100644
index 000000000..9f7926850
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/series-peering-groups.py
@@ -0,0 +1,180 @@
+# 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.
+'Prepares descriptors and timeseries for peering group metrics.'
+
+import itertools
+import logging
+
+from . import MetricDescriptor, TimeSeries, register_timeseries
+
+DESCRIPTOR_ATTRS = {
+ 'forwarding_rules_l4_available':
+ 'L4 fwd rules limit per peering group',
+ 'forwarding_rules_l4_used':
+ 'L4 fwd rules used per peering group',
+ 'forwarding_rules_l4_used_ratio':
+ 'L4 fwd rules used ratio per peering group',
+ 'forwarding_rules_l7_available':
+ 'L7 fwd rules limit per peering group',
+ 'forwarding_rules_l7_used':
+ 'L7 fwd rules used per peering group',
+ 'forwarding_rules_l7_used_ratio':
+ 'L7 fwd rules used ratio per peering group',
+ 'instances_available':
+ 'Instance limit per peering group',
+ 'instances_used':
+ 'Instance used per peering group',
+ 'instances_used_ratio':
+ 'Instance used ratio per peering group',
+ 'routes_dynamic_available':
+ 'Dynamic route limit per peering group',
+ 'routes_dynamic_used':
+ 'Dynamic route used per peering group',
+ 'routes_dynamic_used_ratio':
+ 'Dynamic route used ratio per peering group',
+ 'routes_static_available':
+ 'Static route limit per peering group',
+ 'routes_static_used':
+ 'Static route used per peering group',
+ 'routes_static_used_ratio':
+ 'Static route used ratio per peering group',
+}
+LIMITS = {
+ 'forwarding_rules_l4': {
+ 'pg': ('INTERNAL_FORWARDING_RULES_PER_PEERING_GROUP', 500),
+ 'prj': ('INTERNAL_FORWARDING_RULES_PER_NETWORK', 500)
+ },
+ 'forwarding_rules_l7': {
+ 'pg': ('INTERNAL_MANAGED_FORWARDING_RULES_PER_PEERING_GROUP', 175),
+ 'prj': ('INTERNAL_MANAGED_FORWARDING_RULES_PER_NETWORK', 75)
+ },
+ 'instances': {
+ 'pg': ('INSTANCES_PER_PEERING_GROUP', 15500),
+ 'prj': ('INSTANCES_PER_NETWORK_GLOBAL', 15000)
+ },
+ 'routes_static': {
+ 'pg': ('STATIC_ROUTES_PER_PEERING_GROUP', 300),
+ 'prj': ('ROUTES', 250)
+ },
+ 'routes_dynamic': {
+ 'pg': ('DYNAMIC_ROUTES_PER_PEERING_GROUP', 300),
+ 'prj': ('', 100)
+ }
+}
+LOGGER = logging.getLogger('net-dash.timeseries.peerings')
+
+
+def _count_forwarding_rules_l4(resources, network_ids):
+ 'Returns count of L4 forwarding rules for specified network ids.'
+ return len([
+ r for r in resources['forwarding_rules'].values() if
+ r['network'] in network_ids and r['load_balancing_scheme'] == 'INTERNAL'
+ ])
+
+
+def _count_forwarding_rules_l7(resources, network_ids):
+ 'Returns count of L7 forwarding rules for specified network ids.'
+ return len([
+ r for r in resources['forwarding_rules'].values()
+ if r['network'] in network_ids and
+ r['load_balancing_scheme'] == 'INTERNAL_MANAGED'
+ ])
+
+
+def _count_instances(resources, network_ids):
+ 'Returns count of instances for specified network ids.'
+ count = 0
+ for i in resources['instances'].values():
+ if any(n['network'] in network_ids for n in i['networks']):
+ count += 1
+ return count
+
+
+def _count_routes_static(resources, network_ids):
+ 'Returns count of static routes for specified network ids.'
+ return len(
+ [r for r in resources['routes'].values() if r['network'] in network_ids])
+
+
+def _count_routes_dynamic(resources, network_ids):
+ 'Returns count of dynamic routes for specified network ids.'
+ return sum([
+ sum(v.values())
+ for k, v in resources['routes_dynamic'].items()
+ if k in network_ids
+ ])
+
+
+def _get_limit_max(quota, network_id, project_id, resource_name):
+ 'Returns maximum limit value in project / peering group / network limits.'
+ pg_name, pg_default = LIMITS[resource_name]['pg']
+ prj_name, prj_default = LIMITS[resource_name]['prj']
+ network_quota = quota.get(network_id, {})
+ project_quota = quota.get(project_id, {}).get('global', {})
+ return max([
+ network_quota.get(pg_name, 0),
+ project_quota.get(prj_name, prj_default),
+ project_quota.get(pg_name, pg_default)
+ ])
+
+
+def _get_limit(quota, network, resource_name):
+ 'Computes and returns peering group limit.'
+ # reference https://cloud.google.com/vpc/docs/quota#vpc-peering-ilb-example
+ # step 1 - vpc_max = max(vpc limit, pg limit)
+ vpc_max = _get_limit_max(quota, network['self_link'], network['project_id'],
+ resource_name)
+ # step 2 - peers_max = [max(vpc limit, pg limit) for v in peered vpcs]
+ # step 3 - peers_min = min(peers_max)
+ peers_min = min([
+ _get_limit_max(quota, p['network'], p['project_id'], resource_name)
+ for p in network['peerings']
+ ])
+ # step 4 - max(vpc_max, peers_min)
+ return max([vpc_max, peers_min])
+
+
+def _peering_group_timeseries(resources, network):
+ 'Computes and returns peering group timeseries for network.'
+ if len(network['peerings']) == 0:
+ return
+ network_ids = [network['self_link']
+ ] + [p['network'] for p in network['peerings']]
+ for resource_name in LIMITS:
+ limit = _get_limit(resources['quota'], network, resource_name)
+ func = globals().get(f'_count_{resource_name}')
+ if not func or not callable(func):
+ LOGGER.critical(f'no handler for {resource_name} or handler not callable')
+ continue
+ count = func(resources, network_ids)
+ labels = {'project': network['project_id'], 'network': network['name']}
+ yield TimeSeries(f'peering_group/{resource_name}_used', count, labels)
+ yield TimeSeries(f'peering_group/{resource_name}_available', limit, labels)
+ yield TimeSeries(f'peering_group/{resource_name}_used_ratio', count / limit,
+ labels)
+
+
+@register_timeseries
+def timeseries(resources):
+ 'Returns peering group timeseries for all networks.'
+ LOGGER.info('timeseries')
+ # returns metric descriptors
+ for dtype, name in DESCRIPTOR_ATTRS.items():
+ yield MetricDescriptor(f'peering_group/{dtype}', name,
+ ('project', 'network'), dtype.endswith('ratio'))
+ # chain timeseries for each network and return each one individually
+ results = itertools.chain(*(_peering_group_timeseries(resources, n)
+ for n in resources['networks'].values()))
+ for result in results:
+ yield result
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py b/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py
new file mode 100644
index 000000000..82e06009d
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py
@@ -0,0 +1,101 @@
+# 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.
+'Prepares descriptors and timeseries for subnetwork-level metrics.'
+
+import collections
+import ipaddress
+import itertools
+import logging
+
+from . import MetricDescriptor, TimeSeries, register_timeseries
+
+DESCRIPTOR_ATTRS = {
+ 'addresses_available': 'Address limit per psa range',
+ 'addresses_used': 'Addresses used per psa range',
+ 'addresses_used_ratio': 'Addresses used ratio per psa range'
+}
+LOGGER = logging.getLogger('net-dash.timeseries.psa')
+
+
+def _sql_addresses(sql_instances):
+ 'Returns counts of Cloud SQL instances per PSA range.'
+ for v in sql_instances.values():
+ if not v['ipAddresses']:
+ continue
+ # 1 IP for the instance + 1 IP for the ILB + 1 IP if HA
+ yield v['ipAddresses'][
+ 0], 2 if v['availabilityType'] != 'REGIONAL' else 3, v['network']
+
+
+def _filestore_addresses(filestore_instances):
+ 'Returns counts of Filestore instances per PSA range.'
+ for v in filestore_instances.values():
+ if not v['ipAddresses'] or not v['reservedIpRange']:
+ continue
+ # Subnet size (reservedIpRange) can be /29, /26 or /24
+ yield v['ipAddresses'][0], ipaddress.ip_network(
+ v['reservedIpRange']).num_addresses, v['network']
+
+
+def _memorystore_addresses(memorystore_instances):
+ 'Returns counts of Memorystore (Redis) instances per PSA range.'
+ for v in memorystore_instances.values():
+ if not v['reservedIpRange'] or v['reservedIpRange'] == '':
+ continue
+ # Subnet size (reservedIpRange) can be minimum /28 or /29
+ yield v['host'], ipaddress.ip_network(
+ v['reservedIpRange']).num_addresses, v['network']
+
+
+@register_timeseries
+def timeseries(resources):
+ 'Returns used/available/ratio timeseries for addresses by PSA ranges.'
+ LOGGER.info('timeseries')
+ for dtype, name in DESCRIPTOR_ATTRS.items():
+ yield MetricDescriptor(f'network/psa/{dtype}', name,
+ ('project', 'network', 'subnetwork'),
+ dtype.endswith('ratio'))
+ psa_nets = {
+ k: {
+ 'network_link':
+ v['network'],
+ 'network_prefix':
+ ipaddress.ip_network('{}/{}'.format(v['address'],
+ v['prefixLength']))
+ } for k, v in resources['global_addresses'].items() if v['prefixLength']
+ }
+ psa_counts = {}
+ for address, ip_count, network in itertools.chain(
+ _sql_addresses(resources.get('sql_instances', {})),
+ _filestore_addresses(resources.get('filestore_instances', {})),
+ _memorystore_addresses(resources.get('memorystore_instances', {}))):
+ ip_address = ipaddress.ip_address(address)
+ for k, v in psa_nets.items():
+ if network == v['network_link'] and ip_address in v['network_prefix']:
+ psa_counts[k] = psa_counts.get(k, 0) + ip_count
+ break
+
+ for k, v in psa_counts.items():
+ max_ips = psa_nets[k]['network_prefix'].num_addresses - 4
+ psa_range = resources['global_addresses'][k]
+ labels = {
+ 'network': psa_range['network'],
+ 'project': psa_range['project_id'],
+ 'psa_range': psa_range['name']
+ }
+
+ yield TimeSeries('network/psa/addresses_available', max_ips, labels)
+ yield TimeSeries('network/psa/addresses_used', v, labels)
+ yield TimeSeries('network/psa/addresses_used_ratio',
+ 0 if v == 0 else v / max_ips, labels)
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/series-routes.py b/blueprints/cloud-operations/network-dashboard/src/plugins/series-routes.py
new file mode 100644
index 000000000..89011215c
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/series-routes.py
@@ -0,0 +1,93 @@
+# 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.
+'Prepares descriptors and timeseries for network-level route metrics.'
+
+import itertools
+import logging
+
+from . import MetricDescriptor, TimeSeries, register_timeseries
+
+DESCRIPTOR_ATTRS = {
+ 'network/routes_dynamic_used':
+ 'Dynamic routes limit per network',
+ 'network/routes_dynamic_available':
+ 'Dynamic routes used per network',
+ 'network/routes_dynamic_used_ratio':
+ 'Dynamic routes used ratio per network',
+ 'network/routes_static_used':
+ 'Static routes limit per network',
+ 'project/routes_dynamic_used':
+ 'Dynamic routes limit per project',
+ 'project/routes_dynamic_available':
+ 'Dynamic routes used per project',
+ 'project/routes_dynamic_used_ratio':
+ 'Dynamic routes used ratio per project',
+ 'project/routes_static_used':
+ 'Static routes limit per project',
+ 'project/routes_static_available':
+ 'Static routes used per project',
+ 'project/routes_static_used_ratio':
+ 'Static routes used ratio per project'
+}
+LIMITS = {'ROUTES': 250, 'ROUTES_DYNAMIC': 100}
+LOGGER = logging.getLogger('net-dash.timeseries.routes')
+
+
+def _dynamic(resources):
+ 'Computes network-level timeseries for dynamic routes.'
+ for network_id, router_counts in resources['routes_dynamic'].items():
+ network = resources['networks'][network_id]
+ count = sum(router_counts.values())
+ labels = {'project': network['project_id'], 'network': network['name']}
+ limit = LIMITS['ROUTES_DYNAMIC']
+ yield TimeSeries('network/routes_dynamic_used', count, labels)
+ yield TimeSeries('network/routes_dynamic_available', limit, labels)
+ yield TimeSeries('network/routes_dynamic_used_ratio', count / limit, labels)
+
+
+def _static(resources):
+ 'Computes network and project-level timeseries for dynamic routes.'
+ filter = lambda v: v['next_hop_type'] in ('peering', 'network')
+ routes = itertools.filterfalse(filter, resources['routes'].values())
+ grouped = itertools.groupby(routes, lambda v: v['network'])
+ project_counts = {}
+ for network_id, elements in grouped:
+ network = resources['networks'].get(network_id)
+ count = len(list(elements))
+ labels = {'project': network['project_id'], 'network': network['name']}
+ yield TimeSeries('network/routes_static_used', count, labels)
+ project_counts[network['project_id']] = project_counts.get(
+ network['project_id'], 0) + count
+ for project_id, count in project_counts.items():
+ labels = {'project': project_id}
+ quota = resources['quota'][project_id]['global']
+ limit = quota.get('ROUTES', LIMITS['ROUTES'])
+ yield TimeSeries('project/routes_static_used', count, labels)
+ yield TimeSeries('project/routes_static_available', limit, labels)
+ yield TimeSeries('project/routes_static_used_ratio', count / limit, labels)
+
+
+@register_timeseries
+def timeseries(resources):
+ 'Returns used/available/ratio timeseries by network and project.'
+ LOGGER.info('timeseries')
+ # return descriptors
+ for dtype, name in DESCRIPTOR_ATTRS.items():
+ labels = ('project') if dtype.startswith('project') else ('project',
+ 'network')
+ yield MetricDescriptor(dtype, name, labels, dtype.endswith('ratio'))
+ # chain static and dynamic route timeseries then return each one individually
+ results = itertools.chain(_static(resources), _dynamic(resources))
+ for result in results:
+ yield result
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/series-subnets.py b/blueprints/cloud-operations/network-dashboard/src/plugins/series-subnets.py
new file mode 100644
index 000000000..a9f0a5f30
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/series-subnets.py
@@ -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.
+'Prepares descriptors and timeseries for subnetwork-level metrics.'
+
+import collections
+import ipaddress
+import itertools
+import logging
+
+from . import MetricDescriptor, TimeSeries, register_timeseries
+
+DESCRIPTOR_ATTRS = {
+ 'addresses_available': 'Address limit per subnet',
+ 'addresses_used': 'Addresses used per subnet',
+ 'addresses_used_ratio': 'Addresses used ratio per subnet'
+}
+LOGGER = logging.getLogger('net-dash.timeseries.subnets')
+
+
+def _subnet_addresses(resources):
+ 'Returns count of addresses per subnetwork.'
+ for v in resources['addresses'].values():
+ if v['status'] != 'RESERVED':
+ continue
+ if v['purpose'] in ('GCE_ENDPOINT', 'DNS_RESOLVER'):
+ yield v['subnetwork'], 1
+
+
+def _subnet_forwarding_rules(resources, subnet_nets):
+ 'Returns counts of forwarding rules per subnetwork.'
+ for v in resources['forwarding_rules'].values():
+ if v['load_balancing_scheme'].startswith('INTERNAL'):
+ yield v['subnetwork'], 1
+ continue
+ if v['psc_accepted']:
+ network = resources['networks'].get(v['network'])
+ if not network:
+ LOGGER.warn(f'PSC address for missing network {v["network"]}')
+ continue
+ address = ipaddress.ip_address(v['address'])
+ for subnet_self_link in network['subnetworks']:
+ if address in subnet_nets[subnet_self_link]:
+ yield subnet_self_link, 1
+ break
+ continue
+
+
+def _subnet_instances(resources):
+ 'Returns counts of instances per subnetwork.'
+ vm_networks = itertools.chain.from_iterable(
+ i['networks'] for i in resources['instances'].values())
+ return collections.Counter(v['subnetwork'] for v in vm_networks).items()
+
+
+@register_timeseries
+def timeseries(resources):
+ 'Returns used/available/ratio timeseries for addresses by subnetwork.'
+ LOGGER.info('timeseries')
+ # return descriptors
+ for dtype, name in DESCRIPTOR_ATTRS.items():
+ yield MetricDescriptor(f'subnetwork/{dtype}', name,
+ ('project', 'network', 'subnetwork', 'region'),
+ dtype.endswith('ratio'))
+ # aggregate per-resource counts in total per-subnet counts
+ subnet_nets = {
+ k: ipaddress.ip_network(v['cidr_range'])
+ for k, v in resources['subnetworks'].items()
+ }
+ # TODO: add counter functions for PSA
+ subnet_counts = {k: 0 for k in resources['subnetworks']}
+ counters = itertools.chain(_subnet_addresses(resources),
+ _subnet_forwarding_rules(resources, subnet_nets),
+ _subnet_instances(resources))
+ for subnet_self_link, count in counters:
+ subnet_counts[subnet_self_link] += count
+ # compute and return metrics
+ for subnet_self_link, count in subnet_counts.items():
+ max_ips = subnet_nets[subnet_self_link].num_addresses - 4
+ subnet = resources['subnetworks'][subnet_self_link]
+ labels = {
+ 'network': resources['networks'][subnet['network']]['name'],
+ 'project': subnet['project_id'],
+ 'region': subnet['region'],
+ 'subnetwork': subnet['name']
+ }
+ yield TimeSeries('subnetwork/addresses_available', max_ips, labels)
+ yield TimeSeries('subnetwork/addresses_used', count, labels)
+ yield TimeSeries('subnetwork/addresses_used_ratio',
+ 0 if count == 0 else count / max_ips, labels)
diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/utils.py b/blueprints/cloud-operations/network-dashboard/src/plugins/utils.py
new file mode 100644
index 000000000..5be659988
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/plugins/utils.py
@@ -0,0 +1,101 @@
+# 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.
+'Utility functions for API requests and responses.'
+
+import itertools
+import json
+import logging
+import re
+
+from . import HTTPRequest, PluginError
+
+MP_PART = '''\
+Content-Type: application/http
+MIME-Version: 1.0
+Content-Transfer-Encoding: binary
+
+GET {}?alt=json HTTP/1.1
+Content-Type: application/json
+MIME-Version: 1.0
+Content-Length: 0
+Accept: application/json
+Accept-Encoding: gzip, deflate
+Host: compute.googleapis.com
+
+'''
+RE_URL = re.compile(r'nextPageToken=[^&]+&?')
+
+
+def batched(iterable, n):
+ 'Batches data into lists of length n. The last batch may be shorter.'
+ # batched('ABCDEFG', 3) --> ABC DEF G
+ if n < 1:
+ raise ValueError('n must be at least one')
+ it = iter(iterable)
+ while (batch := list(itertools.islice(it, n))):
+ yield batch
+
+
+def parse_cai_results(data, name, resource_type=None, method='search'):
+ 'Parses an asset API response and returns individual results.'
+ results = data.get('results' if method == 'search' else 'assets')
+ if not results:
+ logging.info(f'no results for {name}')
+ return
+ for result in results:
+ if resource_type and result['assetType'] != resource_type:
+ logging.warn(f'result for wrong type {result["assetType"]}')
+ continue
+ yield result
+
+
+def parse_page_token(data, url):
+ 'Detect next page token in result and return next page URL.'
+ page_token = data.get('nextPageToken')
+ if page_token:
+ logging.info(f'page token {page_token}')
+ if page_token:
+ return RE_URL.sub(f'pageToken={page_token}&', url)
+
+
+def poor_man_mp_request(urls, boundary='1234567890'):
+ 'Bundles URLs into a single multipart mixed batched request.'
+ boundary = f'--{boundary}'
+ data = [boundary]
+ for url in urls:
+ data += ['\n', MP_PART.format(url), boundary]
+ data.append('--\n')
+ headers = {'content-type': f'multipart/mixed; boundary={boundary[2:]}'}
+ return HTTPRequest('https://compute.googleapis.com/batch/compute/v1', headers,
+ ''.join(data), False)
+
+
+def poor_man_mp_response(content_type, content):
+ 'Parses a multipart mixed response and returns individual parts.'
+ try:
+ _, boundary = content_type.split('=')
+ except ValueError:
+ raise PluginError('no boundary found in content type')
+ content = content.decode('utf-8').strip()[:-2]
+ if boundary not in content:
+ raise PluginError('MIME boundary not found')
+ for part in content.split(f'--{boundary}'):
+ part = part.strip()
+ if not part:
+ continue
+ try:
+ mime_header, header, body = part.split('\r\n\r\n', 3)
+ except ValueError:
+ raise PluginError('cannot parse MIME part')
+ yield json.loads(body)
diff --git a/blueprints/cloud-operations/network-dashboard/src/requirements.txt b/blueprints/cloud-operations/network-dashboard/src/requirements.txt
new file mode 100644
index 000000000..3ca529bc3
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/requirements.txt
@@ -0,0 +1,4 @@
+click==8.1.3
+google-auth==2.14.1
+PyYAML==6.0
+requests==2.28.1
diff --git a/blueprints/cloud-operations/network-dashboard/src/tools/remove-descriptors.py b/blueprints/cloud-operations/network-dashboard/src/tools/remove-descriptors.py
new file mode 100755
index 000000000..93b1110e4
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/src/tools/remove-descriptors.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+# 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.
+'Delete metric descriptors matching filter.'
+
+import json
+import logging
+
+import click
+import google.auth
+
+from google.auth.transport.requests import AuthorizedSession
+
+HEADERS = {'content-type': 'application/json'}
+HTTP = AuthorizedSession(google.auth.default()[0])
+URL_DELETE = 'https://monitoring.googleapis.com/v3/{}'
+URL_LIST = (
+ 'https://monitoring.googleapis.com/v3/projects/{}'
+ '/metricDescriptors?filter=metric.type=starts_with("custom.googleapis.com/netmon/")'
+ '&alt=json')
+
+
+def fetch(url, delete=False):
+ 'Minimal HTTP client interface for API calls.'
+ # try
+ try:
+ if not delete:
+ response = HTTP.get(url, headers=HEADERS)
+ else:
+ response = HTTP.delete(url)
+ except google.auth.exceptions.RefreshError as e:
+ raise SystemExit(e.args[0])
+ if response.status_code != 200:
+ logging.critical(f'response code {response.status_code} for URL {url}')
+ logging.critical(response.content)
+ return
+ return response.json()
+
+
+@click.command()
+@click.option('--monitoring-project', '-op', required=True, type=str,
+ help='GCP monitoring project where metrics will be stored.')
+def main(monitoring_project):
+ 'Module entry point.'
+ # if not click.confirm('Do you want to continue?'):
+ # raise SystemExit(0)
+ logging.info('fetching descriptors')
+ result = fetch(URL_LIST.format(monitoring_project))
+ descriptors = result.get('metricDescriptors')
+ if not descriptors:
+ raise SystemExit(0)
+ logging.info(f'{len(descriptors)} descriptors')
+ for d in descriptors:
+ name = d['name']
+ logging.info(f'delete {name}')
+ result = fetch(URL_DELETE.format(name), True)
+
+
+if __name__ == '__main__':
+ logging.basicConfig(level=logging.INFO)
+ main()
diff --git a/blueprints/cloud-operations/network-dashboard/tests/README.md b/blueprints/cloud-operations/network-dashboard/tests/README.md
deleted file mode 100644
index 6e4779d45..000000000
--- a/blueprints/cloud-operations/network-dashboard/tests/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Creating here resources to test the Cloud Function and ensuring metrics are correctly populated
\ No newline at end of file
diff --git a/blueprints/cloud-operations/network-dashboard/tests/test.tf b/blueprints/cloud-operations/network-dashboard/tests/test.tf
deleted file mode 100644
index bb9d6d317..000000000
--- a/blueprints/cloud-operations/network-dashboard/tests/test.tf
+++ /dev/null
@@ -1,287 +0,0 @@
-/**
- * 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.
- */
-
-resource "google_folder" "test-net-dash" {
- display_name = "test-net-dash"
- parent = "organizations/${var.organization_id}"
-}
-
-##### Creating host projects, VPCs, service projects #####
-
-module "project-hub" {
- source = "../../../../modules/project"
- name = "test-host-hub"
- parent = google_folder.test-net-dash.name
- prefix = var.prefix
- billing_account = var.billing_account
- services = var.project_vm_services
-
- shared_vpc_host_config = {
- enabled = true
- }
-}
-
-module "vpc-hub" {
- source = "../../../../modules/net-vpc"
- project_id = module.project-hub.project_id
- name = "vpc-hub"
- subnets = [
- {
- ip_cidr_range = "10.0.10.0/24"
- name = "subnet-hub-1"
- region = var.region
- }
- ]
-}
-
-module "project-svc-hub" {
- source = "../../../../modules/project"
- parent = google_folder.test-net-dash.name
- billing_account = var.billing_account
- prefix = var.prefix
- name = "test-svc-hub"
- services = var.project_vm_services
-
- shared_vpc_service_config = {
- attach = true
- host_project = module.project-hub.project_id
- }
-}
-
-module "project-prod" {
- source = "../../../../modules/project"
- name = "test-host-prod"
- parent = google_folder.test-net-dash.name
- prefix = var.prefix
- billing_account = var.billing_account
- services = var.project_vm_services
-
- shared_vpc_host_config = {
- enabled = true
- }
-}
-
-module "vpc-prod" {
- source = "../../../../modules/net-vpc"
- project_id = module.project-prod.project_id
- name = "vpc-prod"
- subnets = [
- {
- ip_cidr_range = "10.0.20.0/24"
- name = "subnet-prod-1"
- region = var.region
- }
- ]
-}
-
-module "project-svc-prod" {
- source = "../../../../modules/project"
- parent = google_folder.test-net-dash.name
- billing_account = var.billing_account
- prefix = var.prefix
- name = "test-svc-prod"
- services = var.project_vm_services
-
- shared_vpc_service_config = {
- attach = true
- host_project = module.project-prod.project_id
- }
-}
-
-module "project-dev" {
- source = "../../../../modules/project"
- name = "test-host-dev"
- parent = google_folder.test-net-dash.name
- prefix = var.prefix
- billing_account = var.billing_account
- services = var.project_vm_services
-
- shared_vpc_host_config = {
- enabled = true
- }
-}
-
-module "vpc-dev" {
- source = "../../../../modules/net-vpc"
- project_id = module.project-dev.project_id
- name = "vpc-dev"
- subnets = [
- {
- ip_cidr_range = "10.0.30.0/24"
- name = "subnet-dev-1"
- region = var.region
- }
- ]
-}
-
-module "project-svc-dev" {
- source = "../../../../modules/project"
- parent = google_folder.test-net-dash.name
- billing_account = var.billing_account
- prefix = var.prefix
- name = "test-svc-dev"
- services = var.project_vm_services
-
- shared_vpc_service_config = {
- attach = true
- host_project = module.project-dev.project_id
- }
-}
-
-##### Creating VPC peerings #####
-
-module "hub-to-prod-peering" {
- source = "../../../../modules/net-vpc-peering"
- local_network = module.vpc-hub.self_link
- peer_network = module.vpc-prod.self_link
-}
-
-module "prod-to-hub-peering" {
- source = "../../../../modules/net-vpc-peering"
- local_network = module.vpc-prod.self_link
- peer_network = module.vpc-hub.self_link
- depends_on = [module.hub-to-prod-peering]
-}
-
-module "hub-to-dev-peering" {
- source = "../../../../modules/net-vpc-peering"
- local_network = module.vpc-hub.self_link
- peer_network = module.vpc-dev.self_link
-}
-
-module "dev-to-hub-peering" {
- source = "../../../../modules/net-vpc-peering"
- local_network = module.vpc-dev.self_link
- peer_network = module.vpc-hub.self_link
- depends_on = [module.hub-to-dev-peering]
-}
-
-##### Creating VMs #####
-
-resource "google_compute_instance" "test-vm-prod1" {
- project = module.project-svc-prod.project_id
- name = "test-vm-prod1"
- machine_type = "f1-micro"
- zone = var.zone
-
- tags = ["${var.region}"]
-
- boot_disk {
- initialize_params {
- image = "debian-cloud/debian-9"
- }
- }
-
- network_interface {
- subnetwork = module.vpc-prod.subnet_self_links["${var.region}/subnet-prod-1"]
- subnetwork_project = module.project-prod.project_id
- }
-
- allow_stopping_for_update = true
-}
-
-resource "google_compute_instance" "test-vm-prod2" {
- project = module.project-prod.project_id
- name = "test-vm-prod2"
- machine_type = "f1-micro"
- zone = var.zone
-
- tags = [var.region]
-
- boot_disk {
- initialize_params {
- image = "debian-cloud/debian-9"
- }
- }
-
- network_interface {
- subnetwork = module.vpc-prod.subnet_self_links["${var.region}/subnet-prod-1"]
- subnetwork_project = module.project-prod.project_id
- }
-
- allow_stopping_for_update = true
-}
-
-resource "google_compute_instance" "test-vm-dev1" {
- count = 10
- project = module.project-svc-dev.project_id
- name = "test-vm-dev${count.index}"
- machine_type = "f1-micro"
- zone = var.zone
-
- tags = ["${var.region}"]
-
- boot_disk {
- initialize_params {
- image = "debian-cloud/debian-9"
- }
- }
-
- network_interface {
- subnetwork = module.vpc-dev.subnet_self_links["${var.region}/subnet-dev-1"]
- subnetwork_project = module.project-dev.project_id
- }
-
- allow_stopping_for_update = true
-}
-
-resource "google_compute_instance" "test-vm-hub1" {
- project = module.project-svc-hub.project_id
- name = "test-vm-hub1"
- machine_type = "f1-micro"
- zone = var.zone
-
- tags = ["${var.region}"]
-
- boot_disk {
- initialize_params {
- image = "debian-cloud/debian-9"
- }
- }
-
- network_interface {
- subnetwork = module.vpc-hub.subnet_self_links["${var.region}/subnet-hub-1"]
- subnetwork_project = module.project-hub.project_id
- }
-
- allow_stopping_for_update = true
-}
-
-# Forwarding Rules
-resource "google_compute_forwarding_rule" "forwarding-rule-dev" {
- count = 10
- name = "forwarding-rule-dev${count.index}"
- project = module.project-svc-dev.project_id
- network = module.vpc-dev.self_link
- subnetwork = module.vpc-dev.subnet_self_links["${var.region}/subnet-dev-1"]
-
- region = var.region
- backend_service = google_compute_region_backend_service.test-backend.id
- ip_protocol = "TCP"
- load_balancing_scheme = "INTERNAL"
- all_ports = true
- allow_global_access = true
-
-}
-
-# backend service
-resource "google_compute_region_backend_service" "test-backend" {
- name = "test-backend"
- region = var.region
- project = module.project-svc-dev.project_id
- protocol = "TCP"
- load_balancing_scheme = "INTERNAL"
-}
diff --git a/blueprints/cloud-operations/network-dashboard/tests/variables.tf b/blueprints/cloud-operations/network-dashboard/tests/variables.tf
deleted file mode 100644
index dd01b29fd..000000000
--- a/blueprints/cloud-operations/network-dashboard/tests/variables.tf
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * 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 "organization_id" {
- description = "The organization id for the associated services"
-}
-
-variable "billing_account" {
- description = "The ID of the billing account to associate this project with"
-}
-
-variable "prefix" {
- description = "Prefix used for resource names."
- type = string
- validation {
- condition = var.prefix != ""
- error_message = "Prefix cannot be empty."
- }
-}
-
-variable "project_vm_services" {
- description = "Service APIs enabled by default in new projects."
- default = [
- "cloudbilling.googleapis.com",
- "compute.googleapis.com",
- "logging.googleapis.com",
- "monitoring.googleapis.com",
- "servicenetworking.googleapis.com",
- ]
-}
-variable "region" {
- description = "Region used to deploy subnets"
- default = "europe-west1"
-}
-
-variable "zone" {
- description = "Zone used to deploy vms"
- default = "europe-west1-b"
-}
diff --git a/blueprints/cloud-operations/network-dashboard/variables.tf b/blueprints/cloud-operations/network-dashboard/variables.tf
deleted file mode 100644
index 2744eed62..000000000
--- a/blueprints/cloud-operations/network-dashboard/variables.tf
+++ /dev/null
@@ -1,89 +0,0 @@
-/**
- * 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 "billing_account" {
- description = "The ID of the billing account to associate this project with."
-}
-
-variable "cf_version" {
- description = "Cloud Function version 2nd Gen or 1st Gen. Possible options: 'V1' or 'V2'.Use CFv2 if your Cloud Function timeouts after 9 minutes. By default it is using CFv1."
- default = "V1"
- validation {
- condition = var.cf_version == "V1" || var.cf_version == "V2"
- error_message = "The value of cf_version must be either V1 or V2."
- }
-}
-
-variable "monitored_folders_list" {
- type = list(string)
- description = "ID of the projects to be monitored (where limits and quotas data will be pulled)."
- default = []
-}
-
-variable "monitored_projects_list" {
- type = list(string)
- description = "ID of the projects to be monitored (where limits and quotas data will be pulled)."
-}
-
-variable "monitoring_project_id" {
- description = "Monitoring project where the dashboard will be created and the solution deployed; a project will be created if set to empty string."
- default = ""
-}
-
-variable "organization_id" {
- description = "The organization id for the associated services."
-}
-
-variable "prefix" {
- description = "Prefix used for resource names."
- type = string
- validation {
- condition = var.prefix != ""
- error_message = "Prefix cannot be empty."
- }
-}
-
-variable "project_monitoring_services" {
- description = "Service APIs enabled in the monitoring project if it will be created."
- default = [
- "artifactregistry.googleapis.com",
- "cloudasset.googleapis.com",
- "cloudbilling.googleapis.com",
- "cloudbuild.googleapis.com",
- "cloudfunctions.googleapis.com",
- "cloudresourcemanager.googleapis.com",
- "cloudscheduler.googleapis.com",
- "compute.googleapis.com",
- "iam.googleapis.com",
- "iamcredentials.googleapis.com",
- "logging.googleapis.com",
- "monitoring.googleapis.com",
- "pubsub.googleapis.com",
- "run.googleapis.com",
- "servicenetworking.googleapis.com",
- "serviceusage.googleapis.com",
- "storage-component.googleapis.com"
- ]
-}
-variable "region" {
- description = "Region used to deploy the cloud functions and scheduler."
- default = "europe-west1"
-}
-
-variable "schedule_cron" {
- description = "Cron format schedule to run the Cloud Function. Default is every 10 minutes."
- default = "*/10 * * * *"
-}
diff --git a/blueprints/cloud-operations/onprem-sa-key-management/versions.tf b/blueprints/cloud-operations/onprem-sa-key-management/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/cloud-operations/onprem-sa-key-management/versions.tf
+++ b/blueprints/cloud-operations/onprem-sa-key-management/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/cloud-operations/packer-image-builder/versions.tf b/blueprints/cloud-operations/packer-image-builder/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/cloud-operations/packer-image-builder/versions.tf
+++ b/blueprints/cloud-operations/packer-image-builder/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/cloud-operations/quota-monitoring/versions.tf b/blueprints/cloud-operations/quota-monitoring/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/cloud-operations/quota-monitoring/versions.tf
+++ b/blueprints/cloud-operations/quota-monitoring/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf b/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf
+++ b/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/README.md b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/README.md
index 534d65992..fd869ae1a 100644
--- a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/README.md
+++ b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/README.md
@@ -7,7 +7,7 @@ This is a helper module to prepare GCP Credentials from Terraform Enterprise wor
module "tfe_oidc" {
source = "./tfc-oidc"
- impersonate_service_account_email = "tfe-test@tfe-test-wif.iam.gserviceaccount.com"
+ impersonate_service_account_email = "tfe-test@tfe-test-wif.iam.gserviceaccount.com"
}
provider "google" {
diff --git a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/versions.tf b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/versions.tf
index a079e99c4..08492c6f9 100644
--- a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/versions.tf
+++ b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/versions.tf
@@ -14,4 +14,16 @@
terraform {
required_version = ">= 1.3.1"
+ required_providers {
+ google = {
+ source = "hashicorp/google"
+ version = ">= 4.50.0" # tftest
+ }
+ google-beta = {
+ source = "hashicorp/google-beta"
+ version = ">= 4.50.0" # tftest
+ }
+ }
}
+
+
diff --git a/blueprints/cloud-operations/unmanaged-instances-healthcheck/main.tf b/blueprints/cloud-operations/unmanaged-instances-healthcheck/main.tf
index d69dddf2b..19f2d467b 100644
--- a/blueprints/cloud-operations/unmanaged-instances-healthcheck/main.tf
+++ b/blueprints/cloud-operations/unmanaged-instances-healthcheck/main.tf
@@ -146,13 +146,11 @@ module "cf-healthchecker" {
name = "cf-healthchecker"
region = var.region
bucket_name = module.cf-restarter.bucket_name
-
bundle_config = {
source_dir = "${path.module}/function/healthchecker"
output_path = "healthchecker.zip"
}
service_account = module.service-account-healthchecker.email
-
function_config = {
entry_point = "HealthCheck"
ingress_settings = null
@@ -161,7 +159,6 @@ module "cf-healthchecker" {
runtime = "go116"
timeout = 300
}
-
environment_variables = {
FILTER = "name = nginx-*"
GRACE_PERIOD = var.grace_period
@@ -171,7 +168,6 @@ module "cf-healthchecker" {
TCP_PORT = var.tcp_port
TIMEOUT = var.timeout
}
-
vpc_connector = {
create = true
name = "hc-connector"
@@ -230,23 +226,25 @@ resource "google_cloud_scheduler_job" "healthcheck-job" {
module "cos-nginx" {
source = "../../../modules/cloud-config-container/nginx"
- test_instance = {
- project_id = module.project.project_id
- zone = "${var.region}-b"
- name = "nginx-test"
- type = "f1-micro"
+}
+
+module "test-vm" {
+ source = "../../../modules/compute-vm"
+ project_id = module.project.project_id
+ zone = "${var.region}-b"
+ name = "nginx-test"
+ boot_disk = {
+ image = "projects/cos-cloud/global/images/family/cos-stable"
+ type = "pd-ssd"
+ size = 10
+ }
+ metadata = {
+ user-data = module.cos-nginx.cloud_config
+ google-logging-enabled = true
+ }
+ network_interfaces = [{
network = module.vpc.self_link
subnetwork = module.vpc.subnet_self_links["${var.region}/apps"]
- }
- test_instance_defaults = {
- disks = {}
- image = null
- metadata = {}
- nat = false
- service_account_roles = [
- "roles/logging.logWriter",
- "roles/monitoring.metricWriter"
- ]
- tags = ["ssh"]
- }
+ }]
+ tags = ["ssh"]
}
diff --git a/blueprints/data-solutions/README.md b/blueprints/data-solutions/README.md
index 4919f29a4..3da4e7e92 100644
--- a/blueprints/data-solutions/README.md
+++ b/blueprints/data-solutions/README.md
@@ -52,6 +52,20 @@ running on a VPC with a private IP and a dedicated Service Account. A GCS bucket
### SQL Server Always On Availability Groups
-This [blueprint](./data-platform-foundations/) implements SQL Server Always On Availability Groups using Fabric modules. It builds a two node cluster with a fileshare witness instance in an existing VPC and adds the necessary firewalling. The actual setup process (apart from Active Directory operations) has been scripted, so that least amount of manual works needs to performed.
+This [blueprint](./sqlserver-alwayson/) implements SQL Server Always On Availability Groups using Fabric modules. It builds a two node cluster with a fileshare witness instance in an existing VPC and adds the necessary firewalling. The actual setup process (apart from Active Directory operations) has been scripted, so that least amount of manual works needs to performed.
+
+
+
+### MLOps with Vertex AI
+
+
+This [blueprint](./vertex-mlops/) implements the infrastructure required to have a fully functional MLOPs environment using Vertex AI: required GCP services activation, Vertex Workbench, GCS buckets to host Vertex AI and Cloud Build artifacts, Artifact Registry docker repository to host custom images, required service accounts, networking and Workload Identity Federation Provider for Github integration (optional).
+
+
+
+### Shielded Folder
+
+
+This [blueprint](./shielded-folder/) implements an opinionated folder configuration according to GCP best practices. Configurations implemented on the folder would be beneficial to host workloads inheriting constraints from the folder they belong to.
diff --git a/blueprints/data-solutions/cmek-via-centralized-kms/README.md b/blueprints/data-solutions/cmek-via-centralized-kms/README.md
index 88590c74b..3813c90c2 100644
--- a/blueprints/data-solutions/cmek-via-centralized-kms/README.md
+++ b/blueprints/data-solutions/cmek-via-centralized-kms/README.md
@@ -1,8 +1,8 @@
# GCE and GCS CMEK via centralized Cloud KMS
-This example creates a sample centralized [Cloud KMS](https://cloud.google.com/kms?hl=it) configuration, and uses it to implement CMEK for [Cloud Storage](https://cloud.google.com/storage/docs/encryption/using-customer-managed-keys) and [Compute Engine](https://cloud.google.com/compute/docs/disks/customer-managed-encryption) in a separate project.
+This example creates a sample centralized [Cloud KMS](https://cloud.google.com/kms?hl=it) configuration, and uses it to implement CMEK for [Cloud Storage](https://cloud.google.com/storage/docs/encryption/using-customer-managed-keys) and [Compute Engine](https://cloud.google.com/compute/docs/disks/customer-managed-encryption) in a service project.
-The example is designed to match real-world use cases with a minimum amount of resources, and be used as a starting point for scenarios where application projects implement CMEK using keys managed by a central team. It also includes the IAM wiring needed to make such scenarios work.
+The example is designed to match real-world use cases with a minimum amount of resources, and be used as a starting point for scenarios where application projects implement CMEK using keys managed by a central team. It also includes the IAM wiring needed to make such scenarios work. Regional resources are used in this example, but the same logic will apply for 'dual regional', 'multi regional' or 'global' resources.
This is the high level diagram:
@@ -35,12 +35,10 @@ This sample creates several distinct groups of resources:
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [billing_account](variables.tf#L16) | Billing account id used as default for new projects. | string | ✓ | |
-| [root_node](variables.tf#L45) | The resource name of the parent Folder or Organization. Must be of the form folders/folder_id or organizations/org_id. | string | ✓ | |
-| [location](variables.tf#L21) | The location where resources will be deployed. | string | | "europe" |
-| [project_kms_name](variables.tf#L27) | Name for the new KMS Project. | string | | "my-project-kms-001" |
-| [project_service_name](variables.tf#L33) | Name for the new Service Project. | string | | "my-project-service-001" |
-| [region](variables.tf#L39) | The region where resources will be deployed. | string | | "europe-west1" |
+| [prefix](variables.tf#L21) | Optional prefix used to generate resources names. | string | ✓ | |
+| [project_config](variables.tf#L27) | Provide 'billing_account_id' and 'parent' values if project creation is needed, uses existing 'projects_id' if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | ✓ | |
+| [location](variables.tf#L15) | The location where resources will be deployed. | string | | "europe" |
+| [region](variables.tf#L44) | The region where resources will be deployed. | string | | "europe-west1" |
| [vpc_ip_cidr_range](variables.tf#L50) | Ip range used in the subnet deployef in the Service Project. | string | | "10.0.0.0/20" |
| [vpc_name](variables.tf#L56) | Name of the VPC created in the Service Project. | string | | "local" |
| [vpc_subnet_name](variables.tf#L62) | Name of the subnet created in the Service Project. | string | | "subnet" |
diff --git a/blueprints/data-solutions/cmek-via-centralized-kms/main.tf b/blueprints/data-solutions/cmek-via-centralized-kms/main.tf
index fb7f9fdd1..73f2b7018 100644
--- a/blueprints/data-solutions/cmek-via-centralized-kms/main.tf
+++ b/blueprints/data-solutions/cmek-via-centralized-kms/main.tf
@@ -12,33 +12,61 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+locals {
+ # Needed when you create KMS keys and encrypted resources in the same terraform state but different projects.
+ kms_keys = {
+ gce = "projects/${module.project-kms.project_id}/locations/${var.region}/keyRings/${var.prefix}-${var.region}/cryptoKeys/key-gcs"
+ gcs = "projects/${module.project-kms.project_id}/locations/${var.region}/keyRings/${var.prefix}-${var.region}/cryptoKeys/key-gcs"
+ }
+}
+
###############################################################################
# Projects #
###############################################################################
module "project-service" {
source = "../../../modules/project"
- name = var.project_service_name
- parent = var.root_node
- billing_account = var.billing_account
+ name = var.project_config.project_ids.service
+ parent = var.project_config.parent
+ billing_account = var.project_config.billing_account_id
+ project_create = var.project_config.billing_account_id != null
+ prefix = var.project_config.billing_account_id == null ? null : var.prefix
services = [
"compute.googleapis.com",
"servicenetworking.googleapis.com",
- "storage-component.googleapis.com"
+ "storage.googleapis.com",
+ "storage-component.googleapis.com",
+ ]
+ service_encryption_key_ids = {
+ compute = [
+ local.kms_keys.gce
+ ]
+ storage = [
+ local.kms_keys.gcs
+ ]
+ }
+ service_config = {
+ disable_on_destroy = false, disable_dependent_services = false
+ }
+ depends_on = [
+ module.kms
]
- oslogin = true
}
module "project-kms" {
source = "../../../modules/project"
- name = var.project_kms_name
- parent = var.root_node
- billing_account = var.billing_account
+ name = var.project_config.project_ids.encryption
+ parent = var.project_config.parent
+ billing_account = var.project_config.billing_account_id
+ project_create = var.project_config.billing_account_id != null
+ prefix = var.project_config.billing_account_id == null ? null : var.prefix
services = [
"cloudkms.googleapis.com",
"servicenetworking.googleapis.com"
]
- oslogin = true
+ service_config = {
+ disable_on_destroy = false, disable_dependent_services = false
+ }
}
###############################################################################
@@ -48,11 +76,11 @@ module "project-kms" {
module "vpc" {
source = "../../../modules/net-vpc"
project_id = module.project-service.project_id
- name = var.vpc_name
+ name = "${var.prefix}-vpc"
subnets = [
{
- ip_cidr_range = var.vpc_ip_cidr_range
- name = var.vpc_subnet_name
+ ip_cidr_range = "10.0.0.0/20"
+ name = "${var.prefix}-${var.region}"
region = var.region
}
]
@@ -63,7 +91,7 @@ module "vpc-firewall" {
project_id = module.project-service.project_id
network = module.vpc.name
default_rules_config = {
- admin_ranges = [var.vpc_ip_cidr_range]
+ admin_ranges = ["10.0.0.0/20"]
}
}
@@ -75,22 +103,10 @@ module "kms" {
source = "../../../modules/kms"
project_id = module.project-kms.project_id
keyring = {
- name = "my-keyring",
- location = var.location
+ name = "${var.prefix}-${var.region}",
+ location = var.region
}
keys = { key-gce = null, key-gcs = null }
- key_iam = {
- key-gce = {
- "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [
- "serviceAccount:${module.project-service.service_accounts.robots.compute}",
- ]
- },
- key-gcs = {
- "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [
- "serviceAccount:${module.project-service.service_accounts.robots.storage}",
- ]
- }
- }
}
###############################################################################
@@ -101,10 +117,10 @@ module "vm_example" {
source = "../../../modules/compute-vm"
project_id = module.project-service.project_id
zone = "${var.region}-b"
- name = "kms-vm"
+ name = "${var.prefix}-vm"
network_interfaces = [{
network = module.vpc.self_link,
- subnetwork = module.vpc.subnet_self_links["${var.region}/subnet"],
+ subnetwork = module.vpc.subnet_self_links["${var.region}/${var.prefix}-${var.region}"],
nat = false,
addresses = null
}]
@@ -127,7 +143,7 @@ module "vm_example" {
encryption = {
encrypt_boot = true
disk_encryption_key_raw = null
- kms_key_self_link = module.kms.key_ids.key-gce
+ kms_key_self_link = local.kms_keys.gce
}
}
@@ -138,7 +154,9 @@ module "vm_example" {
module "kms-gcs" {
source = "../../../modules/gcs"
project_id = module.project-service.project_id
- prefix = "my-bucket-001"
- name = "kms-gcs"
- encryption_key = module.kms.keys.key-gcs.id
+ prefix = var.prefix
+ name = "${var.prefix}-bucket"
+ location = var.region
+ storage_class = "REGIONAL"
+ encryption_key = local.kms_keys.gcs
}
diff --git a/blueprints/data-solutions/cmek-via-centralized-kms/variables.tf b/blueprints/data-solutions/cmek-via-centralized-kms/variables.tf
index 737bde3dd..5d35351c9 100644
--- a/blueprints/data-solutions/cmek-via-centralized-kms/variables.tf
+++ b/blueprints/data-solutions/cmek-via-centralized-kms/variables.tf
@@ -12,28 +12,33 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-
-variable "billing_account" {
- description = "Billing account id used as default for new projects."
- type = string
-}
-
variable "location" {
description = "The location where resources will be deployed."
type = string
default = "europe"
}
-variable "project_kms_name" {
- description = "Name for the new KMS Project."
+variable "prefix" {
+ description = "Optional prefix used to generate resources names."
type = string
- default = "my-project-kms-001"
+ nullable = false
}
-variable "project_service_name" {
- description = "Name for the new Service Project."
- type = string
- default = "my-project-service-001"
+variable "project_config" {
+ description = "Provide 'billing_account_id' and 'parent' values if project creation is needed, uses existing 'projects_id' if null. Parent is in 'folders/nnn' or 'organizations/nnn' format."
+ type = object({
+ billing_account_id = optional(string, null)
+ parent = optional(string, null)
+ project_ids = optional(object({
+ encryption = string
+ service = string
+ }), {
+ encryption = "encryption",
+ service = "service"
+ }
+ )
+ })
+ nullable = false
}
variable "region" {
@@ -42,11 +47,6 @@ variable "region" {
default = "europe-west1"
}
-variable "root_node" {
- description = "The resource name of the parent Folder or Organization. Must be of the form folders/folder_id or organizations/org_id."
- type = string
-}
-
variable "vpc_ip_cidr_range" {
description = "Ip range used in the subnet deployef in the Service Project."
type = string
diff --git a/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf b/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf
+++ b/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/data-solutions/data-platform-foundations/03-composer.tf b/blueprints/data-solutions/data-platform-foundations/03-composer.tf
index 2622ffa20..f806f0e51 100644
--- a/blueprints/data-solutions/data-platform-foundations/03-composer.tf
+++ b/blueprints/data-solutions/data-platform-foundations/03-composer.tf
@@ -14,6 +14,41 @@
# tfdoc:file:description Orchestration Cloud Composer definition.
+locals {
+ env_variables = {
+ BQ_LOCATION = var.location
+ DATA_CAT_TAGS = try(jsonencode(module.common-datacatalog.tags), "{}")
+ DF_KMS_KEY = try(var.service_encryption_keys.dataflow, "")
+ DRP_PRJ = module.drop-project.project_id
+ DRP_BQ = module.drop-bq-0.dataset_id
+ DRP_GCS = module.drop-cs-0.url
+ DRP_PS = module.drop-ps-0.id
+ DWH_LAND_PRJ = module.dwh-lnd-project.project_id
+ DWH_LAND_BQ_DATASET = module.dwh-lnd-bq-0.dataset_id
+ DWH_LAND_GCS = module.dwh-lnd-cs-0.url
+ DWH_CURATED_PRJ = module.dwh-cur-project.project_id
+ DWH_CURATED_BQ_DATASET = module.dwh-cur-bq-0.dataset_id
+ DWH_CURATED_GCS = module.dwh-cur-cs-0.url
+ DWH_CONFIDENTIAL_PRJ = module.dwh-conf-project.project_id
+ DWH_CONFIDENTIAL_BQ_DATASET = module.dwh-conf-bq-0.dataset_id
+ DWH_CONFIDENTIAL_GCS = module.dwh-conf-cs-0.url
+ GCP_REGION = var.region
+ LOD_PRJ = module.load-project.project_id
+ LOD_GCS_STAGING = module.load-cs-df-0.url
+ LOD_NET_VPC = local.load_vpc
+ LOD_NET_SUBNET = local.load_subnet
+ LOD_SA_DF = module.load-sa-df-0.email
+ ORC_PRJ = module.orch-project.project_id
+ ORC_GCS = module.orch-cs-0.url
+ ORC_GCS_TMP_DF = module.orch-cs-df-template.url
+ TRF_PRJ = module.transf-project.project_id
+ TRF_GCS_STAGING = module.transf-cs-df-0.url
+ TRF_NET_VPC = local.transf_vpc
+ TRF_NET_SUBNET = local.transf_subnet
+ TRF_SA_DF = module.transf-sa-df-0.email
+ TRF_SA_BQ = module.transf-sa-bq-0.email
+ }
+}
module "orch-sa-cmp-0" {
source = "../../../modules/iam-service-account"
project_id = module.orch-project.project_id
@@ -27,21 +62,51 @@ module "orch-sa-cmp-0" {
}
resource "google_composer_environment" "orch-cmp-0" {
- provider = google-beta
- project = module.orch-project.project_id
- name = "${var.prefix}-orc-cmp-0"
- region = var.region
+ count = var.composer_config.disable_deployment == true ? 0 : 1
+ project = module.orch-project.project_id
+ name = "${var.prefix}-orc-cmp-0"
+ region = var.region
config {
- node_count = var.composer_config.node_count
+ software_config {
+ airflow_config_overrides = try(var.composer_config.software_config.airflow_config_overrides, null)
+ pypi_packages = try(var.composer_config.software_config.pypi_packages, null)
+ env_variables = merge(try(var.composer_config.software_config.env_variables, null), local.env_variables)
+ image_version = try(var.composer_config.software_config.image_version, null)
+ }
+ dynamic "workloads_config" {
+ for_each = (try(var.composer_config.workloads_config, null) != null ? { 1 = 1 } : {})
+
+ content {
+ scheduler {
+ cpu = try(var.composer_config.workloads_config.scheduler.cpu, null)
+ memory_gb = try(var.composer_config.workloads_config.scheduler.memory_gb, null)
+ storage_gb = try(var.composer_config.workloads_config.scheduler.storage_gb, null)
+ count = try(var.composer_config.workloads_config.scheduler.count, null)
+ }
+ web_server {
+ cpu = try(var.composer_config.workloads_config.web_server.cpu, null)
+ memory_gb = try(var.composer_config.workloads_config.web_server.memory_gb, null)
+ storage_gb = try(var.composer_config.workloads_config.web_server.storage_gb, null)
+ }
+ worker {
+ cpu = try(var.composer_config.workloads_config.worker.cpu, null)
+ memory_gb = try(var.composer_config.workloads_config.worker.memory_gb, null)
+ storage_gb = try(var.composer_config.workloads_config.worker.storage_gb, null)
+ min_count = try(var.composer_config.workloads_config.worker.min_count, null)
+ max_count = try(var.composer_config.workloads_config.worker.max_count, null)
+ }
+ }
+ }
+
+ environment_size = var.composer_config.environment_size
+
node_config {
- zone = "${var.region}-b"
- service_account = module.orch-sa-cmp-0.email
network = local.orch_vpc
subnetwork = local.orch_subnet
- tags = ["composer-worker", "http-server", "https-server"]
- enable_ip_masq_agent = true
+ service_account = module.orch-sa-cmp-0.email
+ enable_ip_masq_agent = "true"
+ tags = ["composer-worker"]
ip_allocation_policy {
- use_ip_aliases = "true"
cluster_secondary_range_name = try(
var.network_config.composer_secondary_ranges.pods, "pods"
)
@@ -58,80 +123,20 @@ resource "google_composer_environment" "orch-cmp-0" {
master_ipv4_cidr_block = try(
var.network_config.composer_ip_ranges.gke_master, "10.20.11.0/28"
)
- web_server_ipv4_cidr_block = try(
- var.network_config.composer_ip_ranges.web_server, "10.20.11.16/28"
- )
}
- software_config {
- image_version = var.composer_config.airflow_version
- env_variables = merge(
- var.composer_config.env_variables, {
- BQ_LOCATION = var.location
- DATA_CAT_TAGS = try(jsonencode(module.common-datacatalog.tags), "{}")
- DF_KMS_KEY = try(var.service_encryption_keys.dataflow, "")
- DRP_PRJ = module.drop-project.project_id
- DRP_BQ = module.drop-bq-0.dataset_id
- DRP_GCS = module.drop-cs-0.url
- DRP_PS = module.drop-ps-0.id
- DWH_LAND_PRJ = module.dwh-lnd-project.project_id
- DWH_LAND_BQ_DATASET = module.dwh-lnd-bq-0.dataset_id
- DWH_LAND_GCS = module.dwh-lnd-cs-0.url
- DWH_CURATED_PRJ = module.dwh-cur-project.project_id
- DWH_CURATED_BQ_DATASET = module.dwh-cur-bq-0.dataset_id
- DWH_CURATED_GCS = module.dwh-cur-cs-0.url
- DWH_CONFIDENTIAL_PRJ = module.dwh-conf-project.project_id
- DWH_CONFIDENTIAL_BQ_DATASET = module.dwh-conf-bq-0.dataset_id
- DWH_CONFIDENTIAL_GCS = module.dwh-conf-cs-0.url
- DWH_PLG_PRJ = module.dwh-plg-project.project_id
- DWH_PLG_BQ_DATASET = module.dwh-plg-bq-0.dataset_id
- DWH_PLG_GCS = module.dwh-plg-cs-0.url
- GCP_REGION = var.region
- LOD_PRJ = module.load-project.project_id
- LOD_GCS_STAGING = module.load-cs-df-0.url
- LOD_NET_VPC = local.load_vpc
- LOD_NET_SUBNET = local.load_subnet
- LOD_SA_DF = module.load-sa-df-0.email
- ORC_PRJ = module.orch-project.project_id
- ORC_GCS = module.orch-cs-0.url
- TRF_PRJ = module.transf-project.project_id
- TRF_GCS_STAGING = module.transf-cs-df-0.url
- TRF_NET_VPC = local.transf_vpc
- TRF_NET_SUBNET = local.transf_subnet
- TRF_SA_DF = module.transf-sa-df-0.email
- TRF_SA_BQ = module.transf-sa-bq-0.email
- }
- )
- }
-
dynamic "encryption_config" {
for_each = (
- try(local.service_encryption_keys.composer != null, false)
+ try(var.service_encryption_keys[var.region], null) != null
? { 1 = 1 }
: {}
)
content {
- kms_key_name = try(local.service_encryption_keys.composer, null)
+ kms_key_name = try(var.service_encryption_keys[var.region], null)
}
}
-
- # dynamic "web_server_network_access_control" {
- # for_each = toset(
- # var.network_config.web_server_network_access_control == null
- # ? []
- # : [var.network_config.web_server_network_access_control]
- # )
- # content {
- # dynamic "allowed_ip_range" {
- # for_each = toset(web_server_network_access_control.key)
- # content {
- # value = allowed_ip_range.key
- # }
- # }
- # }
- # }
-
}
depends_on = [
google_project_iam_member.shared_vpc,
+ module.orch-project
]
}
diff --git a/blueprints/data-solutions/data-platform-foundations/03-orchestration.tf b/blueprints/data-solutions/data-platform-foundations/03-orchestration.tf
index 2974c1227..a202afdd0 100644
--- a/blueprints/data-solutions/data-platform-foundations/03-orchestration.tf
+++ b/blueprints/data-solutions/data-platform-foundations/03-orchestration.tf
@@ -25,6 +25,11 @@ locals {
? var.network_config.network_self_link
: module.orch-vpc.0.self_link
)
+
+ # Note: This formatting is needed for output purposes since the fabric artifact registry
+ # module doesn't yet expose the docker usage path of a registry folder in the needed format.
+ orch_docker_path = format("%s-docker.pkg.dev/%s/%s",
+ var.region, module.orch-project.project_id, module.orch-artifact-reg.name)
}
module "orch-project" {
@@ -44,6 +49,8 @@ module "orch-project" {
"roles/iam.serviceAccountUser",
"roles/storage.objectAdmin",
"roles/storage.admin",
+ "roles/artifactregistry.admin",
+ "roles/serviceusage.serviceUsageConsumer",
]
}
iam = {
@@ -54,6 +61,9 @@ module "orch-project" {
"roles/bigquery.jobUser" = [
module.orch-sa-cmp-0.iam_email,
]
+ "roles/composer.ServiceAgentV2Ext" = [
+ "serviceAccount:${module.orch-project.service_accounts.robots.composer}"
+ ]
"roles/composer.worker" = [
module.orch-sa-cmp-0.iam_email
]
@@ -62,16 +72,19 @@ module "orch-project" {
]
"roles/storage.objectAdmin" = [
module.orch-sa-cmp-0.iam_email,
+ module.orch-sa-df-build.iam_email,
"serviceAccount:${module.orch-project.service_accounts.robots.composer}",
+ "serviceAccount:${module.orch-project.service_accounts.robots.cloudbuild}",
+ ]
+ "roles/artifactregistry.reader" = [
+ module.load-sa-df-0.iam_email,
+ ]
+ "roles/cloudbuild.serviceAgent" = [
+ module.orch-sa-df-build.iam_email,
]
"roles/storage.objectViewer" = [module.load-sa-df-0.iam_email]
}
oslogin = false
- org_policies = {
- "constraints/compute.requireOsLogin" = {
- enforce = false
- }
- }
services = concat(var.project_services, [
"artifactregistry.googleapis.com",
"bigquery.googleapis.com",
@@ -83,6 +96,7 @@ module "orch-project" {
"compute.googleapis.com",
"container.googleapis.com",
"containerregistry.googleapis.com",
+ "artifactregistry.googleapis.com",
"dataflow.googleapis.com",
"orgpolicy.googleapis.com",
"pubsub.googleapis.com",
@@ -150,3 +164,46 @@ module "orch-nat" {
region = var.region
router_network = module.orch-vpc.0.name
}
+
+module "orch-artifact-reg" {
+ source = "../../../modules/artifact-registry"
+ project_id = module.orch-project.project_id
+ id = "${var.prefix}-app-images"
+ location = var.region
+ format = "DOCKER"
+ description = "Docker repository storing application images e.g. Dataflow, Cloud Run etc..."
+}
+
+module "orch-cs-df-template" {
+ source = "../../../modules/gcs"
+ project_id = module.orch-project.project_id
+ prefix = var.prefix
+ name = "orc-cs-df-template"
+ location = var.region
+ storage_class = "REGIONAL"
+ encryption_key = try(local.service_encryption_keys.storage, null)
+}
+
+module "orch-cs-build-staging" {
+ source = "../../../modules/gcs"
+ project_id = module.orch-project.project_id
+ prefix = var.prefix
+ name = "orc-cs-build-staging"
+ location = var.region
+ storage_class = "REGIONAL"
+ encryption_key = try(local.service_encryption_keys.storage, null)
+}
+
+module "orch-sa-df-build" {
+ source = "../../../modules/iam-service-account"
+ project_id = module.orch-project.project_id
+ prefix = var.prefix
+ name = "orc-sa-df-build"
+ display_name = "Data platform Dataflow build service account"
+ # Note values below should pertain to the system / group / users who are able to
+ # invoke the build via this service account
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = [local.groups_iam.data-engineers]
+ "roles/iam.serviceAccountUser" = [local.groups_iam.data-engineers]
+ }
+}
diff --git a/blueprints/data-solutions/data-platform-foundations/05-datawarehouse.tf b/blueprints/data-solutions/data-platform-foundations/05-datawarehouse.tf
index 879a0e0b1..0db5ce440 100644
--- a/blueprints/data-solutions/data-platform-foundations/05-datawarehouse.tf
+++ b/blueprints/data-solutions/data-platform-foundations/05-datawarehouse.tf
@@ -30,21 +30,6 @@ locals {
"roles/storage.objectViewer",
]
}
- dwh_plg_group_iam = {
- (local.groups.data-engineers) = [
- "roles/bigquery.dataEditor",
- "roles/storage.admin",
- ],
- (local.groups.data-analysts) = [
- "roles/bigquery.dataEditor",
- "roles/bigquery.jobUser",
- "roles/bigquery.metadataViewer",
- "roles/bigquery.user",
- "roles/datacatalog.viewer",
- "roles/datacatalog.tagTemplateViewer",
- "roles/storage.objectAdmin",
- ]
- }
dwh_lnd_iam = {
"roles/bigquery.dataOwner" = [
module.load-sa-df-0.iam_email,
@@ -140,21 +125,6 @@ module "dwh-conf-project" {
}
}
-module "dwh-plg-project" {
- source = "../../../modules/project"
- parent = var.folder_id
- billing_account = var.billing_account_id
- prefix = var.prefix
- name = "dwh-plg${local.project_suffix}"
- group_iam = local.dwh_plg_group_iam
- iam = {}
- services = local.dwh_services
- service_encryption_key_ids = {
- bq = [try(local.service_encryption_keys.bq, null)]
- storage = [try(local.service_encryption_keys.storage, null)]
- }
-}
-
# Bigquery
module "dwh-lnd-bq-0" {
@@ -181,14 +151,6 @@ module "dwh-conf-bq-0" {
encryption_key = try(local.service_encryption_keys.bq, null)
}
-module "dwh-plg-bq-0" {
- source = "../../../modules/bigquery-dataset"
- project_id = module.dwh-plg-project.project_id
- id = "${replace(var.prefix, "-", "_")}_dwh_plg_bq_0"
- location = var.location
- encryption_key = try(local.service_encryption_keys.bq, null)
-}
-
# Cloud storage
module "dwh-lnd-cs-0" {
@@ -223,14 +185,3 @@ module "dwh-conf-cs-0" {
encryption_key = try(local.service_encryption_keys.storage, null)
force_destroy = var.data_force_destroy
}
-
-module "dwh-plg-cs-0" {
- source = "../../../modules/gcs"
- project_id = module.dwh-plg-project.project_id
- prefix = var.prefix
- name = "dwh-plg-cs-0"
- location = var.location
- storage_class = "MULTI_REGIONAL"
- encryption_key = try(local.service_encryption_keys.storage, null)
- force_destroy = var.data_force_destroy
-}
diff --git a/blueprints/data-solutions/data-platform-foundations/IAM.md b/blueprints/data-solutions/data-platform-foundations/IAM.md
index 54d35939b..dd898bd75 100644
--- a/blueprints/data-solutions/data-platform-foundations/IAM.md
+++ b/blueprints/data-solutions/data-platform-foundations/IAM.md
@@ -57,14 +57,6 @@ Legend: + additive, • conditional.
|trf-bq-0
serviceAccount|[roles/bigquery.dataOwner](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataOwner)
[roles/datacatalog.categoryAdmin](https://cloud.google.com/iam/docs/understanding-roles#datacatalog.categoryAdmin) |
|trf-df-0
serviceAccount|[roles/bigquery.dataOwner](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataOwner) |
-## Project dwh-plg
-
-| members | roles |
-|---|---|
-|gcp-data-analysts
group|[roles/bigquery.dataEditor](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataEditor)
[roles/bigquery.jobUser](https://cloud.google.com/iam/docs/understanding-roles#bigquery.jobUser)
[roles/bigquery.metadataViewer](https://cloud.google.com/iam/docs/understanding-roles#bigquery.metadataViewer)
[roles/bigquery.user](https://cloud.google.com/iam/docs/understanding-roles#bigquery.user)
[roles/datacatalog.tagTemplateViewer](https://cloud.google.com/iam/docs/understanding-roles#datacatalog.tagTemplateViewer)
[roles/datacatalog.viewer](https://cloud.google.com/iam/docs/understanding-roles#datacatalog.viewer)
[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) |
-|gcp-data-engineers
group|[roles/bigquery.dataEditor](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataEditor)
[roles/storage.admin](https://cloud.google.com/iam/docs/understanding-roles#storage.admin) |
-|SERVICE_IDENTITY_service-networking
serviceAccount|[roles/servicenetworking.serviceAgent](https://cloud.google.com/iam/docs/understanding-roles#servicenetworking.serviceAgent) +|
-
## Project lod
| members | roles |
@@ -79,11 +71,13 @@ Legend: + additive, • conditional.
| members | roles |
|---|---|
-|gcp-data-engineers
group|[roles/bigquery.dataEditor](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataEditor)
[roles/bigquery.jobUser](https://cloud.google.com/iam/docs/understanding-roles#bigquery.jobUser)
[roles/cloudbuild.builds.editor](https://cloud.google.com/iam/docs/understanding-roles#cloudbuild.builds.editor)
[roles/composer.admin](https://cloud.google.com/iam/docs/understanding-roles#composer.admin)
[roles/composer.environmentAndStorageObjectAdmin](https://cloud.google.com/iam/docs/understanding-roles#composer.environmentAndStorageObjectAdmin)
[roles/iam.serviceAccountUser](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountUser)
[roles/iap.httpsResourceAccessor](https://cloud.google.com/iam/docs/understanding-roles#iap.httpsResourceAccessor)
[roles/storage.admin](https://cloud.google.com/iam/docs/understanding-roles#storage.admin)
[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) |
-|SERVICE_IDENTITY_cloudcomposer-accounts
serviceAccount|[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) |
+|gcp-data-engineers
group|[roles/artifactregistry.admin](https://cloud.google.com/iam/docs/understanding-roles#artifactregistry.admin)
[roles/bigquery.dataEditor](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataEditor)
[roles/bigquery.jobUser](https://cloud.google.com/iam/docs/understanding-roles#bigquery.jobUser)
[roles/cloudbuild.builds.editor](https://cloud.google.com/iam/docs/understanding-roles#cloudbuild.builds.editor)
[roles/composer.admin](https://cloud.google.com/iam/docs/understanding-roles#composer.admin)
[roles/composer.environmentAndStorageObjectAdmin](https://cloud.google.com/iam/docs/understanding-roles#composer.environmentAndStorageObjectAdmin)
[roles/iam.serviceAccountUser](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountUser)
[roles/iap.httpsResourceAccessor](https://cloud.google.com/iam/docs/understanding-roles#iap.httpsResourceAccessor)
[roles/serviceusage.serviceUsageConsumer](https://cloud.google.com/iam/docs/understanding-roles#serviceusage.serviceUsageConsumer)
[roles/storage.admin](https://cloud.google.com/iam/docs/understanding-roles#storage.admin)
[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) |
+|SERVICE_IDENTITY_cloudcomposer-accounts
serviceAccount|[roles/composer.ServiceAgentV2Ext](https://cloud.google.com/iam/docs/understanding-roles#composer.ServiceAgentV2Ext)
[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) |
+|SERVICE_IDENTITY_gcp-sa-cloudbuild
serviceAccount|[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) |
|SERVICE_IDENTITY_service-networking
serviceAccount|[roles/servicenetworking.serviceAgent](https://cloud.google.com/iam/docs/understanding-roles#servicenetworking.serviceAgent) +|
-|load-df-0
serviceAccount|[roles/bigquery.dataEditor](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataEditor)
[roles/storage.objectViewer](https://cloud.google.com/iam/docs/understanding-roles#storage.objectViewer) |
+|load-df-0
serviceAccount|[roles/artifactregistry.reader](https://cloud.google.com/iam/docs/understanding-roles#artifactregistry.reader)
[roles/bigquery.dataEditor](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataEditor)
[roles/storage.objectViewer](https://cloud.google.com/iam/docs/understanding-roles#storage.objectViewer) |
|orc-cmp-0
serviceAccount|[roles/bigquery.jobUser](https://cloud.google.com/iam/docs/understanding-roles#bigquery.jobUser)
[roles/composer.worker](https://cloud.google.com/iam/docs/understanding-roles#composer.worker)
[roles/iam.serviceAccountUser](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountUser)
[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) |
+|orc-sa-df-build
serviceAccount|[roles/cloudbuild.serviceAgent](https://cloud.google.com/iam/docs/understanding-roles#cloudbuild.serviceAgent)
[roles/storage.objectAdmin](https://cloud.google.com/iam/docs/understanding-roles#storage.objectAdmin) |
|trf-df-0
serviceAccount|[roles/bigquery.dataEditor](https://cloud.google.com/iam/docs/understanding-roles#bigquery.dataEditor) |
## Project trf
diff --git a/blueprints/data-solutions/data-platform-foundations/README.md b/blueprints/data-solutions/data-platform-foundations/README.md
index 8da143b29..08b24b211 100644
--- a/blueprints/data-solutions/data-platform-foundations/README.md
+++ b/blueprints/data-solutions/data-platform-foundations/README.md
@@ -21,7 +21,7 @@ The approach adapts to different high-level requirements:
- least privilege principle
- rely on service account impersonation
-The code in this blueprint doesn't address Organization-level configurations (Organization policy, VPC-SC, centralized logs). We expect those elements to be managed by automation stages external to this script like those in [FAST](../../../fast).
+The code in this blueprint doesn't address Organization-level configurations (Organization policy, VPC-SC, centralized logs). We expect those elements to be managed by automation stages external to this script like those in [FAST](../../../fast) and this blueprint deployed on top of them as one of the [stages](../../../fast/stages/3-data-platform/dev/README.md).
### Project structure
@@ -39,14 +39,13 @@ This separation into projects allows adhering to the least-privilege principle b
The script will create the following projects:
- **Drop off** Used to store temporary data. Data is pushed to Cloud Storage, BigQuery, or Cloud PubSub. Resources are configured with a customizable lifecycle policy.
-- **Load** Used to load data from the drop off zone to the data warehouse. The load is made with minimal to zero transformation logic (mainly `cast`). Anonymization or tokenization of Personally Identifiable Information (PII) can be implemented here or in the transformation stage, depending on your requirements. The use of [Cloud Dataflow templates](https://cloud.google.com/dataflow/docs/concepts/dataflow-templates) is recommended.
+- **Load** Used to load data from the drop off zone to the data warehouse. The load is made with minimal to zero transformation logic (mainly `cast`). Anonymization or tokenization of Personally Identifiable Information (PII) can be implemented here or in the transformation stage, depending on your requirements. The use of [Cloud Dataflow templates](https://cloud.google.com/dataflow/docs/concepts/dataflow-templates) is recommended. When you need to handle workloads from different teams, if strong role separation is needed between them, we suggest to customize the scirpt and have separate `Load` projects.
- **Data Warehouse** Several projects distributed across 3 separate layers, to host progressively processed and refined data:
- **Landing - Raw data** Structured Data, stored in relevant formats: structured data stored in BigQuery, unstructured data stored on Cloud Storage with additional metadata stored in BigQuery (for example pictures stored in Cloud Storage and analysis of the images for Cloud Vision API stored in BigQuery).
- **Curated - Cleansed, aggregated and curated data**
- **Confidential - Curated and unencrypted layer**
- - **Playground** Temporary tables that Data Analyst may use to perform R&D on data available in other Data Warehouse layers.
- **Orchestration** Used to host Cloud Composer, which orchestrates all tasks that move data across layers.
-- **Transformation** Used to move data between Data Warehouse layers. We strongly suggest relying on BigQuery Engine to perform the transformations. If BigQuery doesn't have the features needed to perform your transformations, you can use Cloud Dataflow with [Cloud Dataflow templates](https://cloud.google.com/dataflow/docs/concepts/dataflow-templates). This stage can also optionally anonymize or tokenize PII.
+- **Transformation** Used to move data between Data Warehouse layers. We strongly suggest relying on BigQuery Engine to perform the transformations. If BigQuery doesn't have the features needed to perform your transformations, you can use Cloud Dataflow with [Cloud Dataflow templates](https://cloud.google.com/dataflow/docs/concepts/dataflow-templates). This stage can also optionally anonymize or tokenize PII. When you need to handle workloads from different teams, if strong role separation is needed between them, we suggest to customize the scirpt and have separate `Tranformation` projects.
- **Exposure** Used to host resources that share processed data with external systems. Depending on the access pattern, data can be presented via Cloud SQL, BigQuery, or Bigtable. For BigQuery data, we strongly suggest relying on [Authorized views](https://cloud.google.com/bigquery/docs/authorized-views).
### Roles
@@ -80,10 +79,10 @@ We use three groups to control access to resources:
The table below shows a high level overview of roles for each group on each project, using `READ`, `WRITE` and `ADMIN` access patterns for simplicity. For detailed roles please refer to the code.
-|Group|Drop off|Load|Transformation|DHW Landing|DWH Curated|DWH Confidential|DWH Playground|Orchestration|Common|
-|-|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
-|Data Engineers|`ADMIN`|`ADMIN`|`ADMIN`|`ADMIN`|`ADMIN`|`ADMIN`|`ADMIN`|`ADMIN`|`ADMIN`|
-|Data Analysts|-|-|-|-|-|`READ`|`READ`/`WRITE`|-|-|
+|Group|Drop off|Load|Transformation|DHW Landing|DWH Curated|DWH Confidential|Orchestration|Common|
+|-|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
+|Data Engineers|`ADMIN`|`ADMIN`|`ADMIN`|`ADMIN`|`ADMIN`|`ADMIN`|`ADMIN`|`ADMIN`|
+|Data Analysts|-|-|-|-|-|`READ`|-|-|
|Data Security|-|-|-|-|-|-|-|-|`ADMIN`|
You can configure groups via the `groups` variable.
@@ -109,14 +108,13 @@ In both VPC scenarios, you also need these ranges for Composer:
- one /24 for Cloud SQL
- one /28 for the GKE control plane
-- one /28 for the web server
### Resource naming conventions
Resources follow the naming convention described below.
- `prefix-layer` for projects
-- `prefix-layer-prduct` for resources
+- `prefix-layer-product` for resources
- `prefix-layer[2]-gcp-product[2]-counter` for services and service accounts
### Encryption
@@ -221,7 +219,7 @@ module "data-platform" {
prefix = "myprefix"
}
-# tftest modules=42 resources=316
+# tftest modules=43 resources=297
```
## Customizations
@@ -247,31 +245,32 @@ You can find examples in the `[demo](./demo)` folder.
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [billing_account_id](variables.tf#L17) | Billing account id. | string | ✓ | |
-| [folder_id](variables.tf#L53) | Folder to be used for the networking resources in folders/nnnn format. | string | ✓ | |
-| [organization_domain](variables.tf#L98) | Organization domain. | string | ✓ | |
-| [prefix](variables.tf#L103) | Prefix used for resource names. | string | ✓ | |
-| [composer_config](variables.tf#L22) | Cloud Composer config. | object({…}) | | {…} |
-| [data_catalog_tags](variables.tf#L36) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {…} |
-| [data_force_destroy](variables.tf#L47) | Flag to set 'force_destroy' on data services like BiguQery or Cloud Storage. | bool | | false |
-| [groups](variables.tf#L58) | User groups. | map(string) | | {…} |
-| [location](variables.tf#L68) | Location used for multi-regional resources. | string | | "eu" |
-| [network_config](variables.tf#L74) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | object({…}) | | null |
-| [project_services](variables.tf#L112) | List of core services enabled on all projects. | list(string) | | […] |
-| [project_suffix](variables.tf#L123) | Suffix used only for project ids. | string | | null |
-| [region](variables.tf#L129) | Region used for regional resources. | string | | "europe-west1" |
-| [service_encryption_keys](variables.tf#L135) | Cloud KMS to use to encrypt different services. Key location should match service region. | object({…}) | | null |
+| [folder_id](variables.tf#L122) | Folder to be used for the networking resources in folders/nnnn format. | string | ✓ | |
+| [organization_domain](variables.tf#L166) | Organization domain. | string | ✓ | |
+| [prefix](variables.tf#L171) | Prefix used for resource names. | string | ✓ | |
+| [composer_config](variables.tf#L22) | Cloud Composer config. | object({…}) | | {…} |
+| [data_catalog_tags](variables.tf#L105) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {…} |
+| [data_force_destroy](variables.tf#L116) | Flag to set 'force_destroy' on data services like BiguQery or Cloud Storage. | bool | | false |
+| [groups](variables.tf#L127) | User groups. | map(string) | | {…} |
+| [location](variables.tf#L137) | Location used for multi-regional resources. | string | | "eu" |
+| [network_config](variables.tf#L143) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | object({…}) | | null |
+| [project_services](variables.tf#L180) | List of core services enabled on all projects. | list(string) | | […] |
+| [project_suffix](variables.tf#L191) | Suffix used only for project ids. | string | | null |
+| [region](variables.tf#L197) | Region used for regional resources. | string | | "europe-west1" |
+| [service_encryption_keys](variables.tf#L203) | Cloud KMS to use to encrypt different services. Key location should match service region. | object({…}) | | null |
## Outputs
| name | description | sensitive |
|---|---|:---:|
-| [bigquery-datasets](outputs.tf#L17) | BigQuery datasets. | |
-| [demo_commands](outputs.tf#L28) | Demo commands. | |
-| [gcs-buckets](outputs.tf#L41) | GCS buckets. | |
-| [kms_keys](outputs.tf#L55) | Cloud MKS keys. | |
-| [projects](outputs.tf#L60) | GCP Projects informations. | |
-| [vpc_network](outputs.tf#L88) | VPC network. | |
-| [vpc_subnet](outputs.tf#L97) | VPC subnetworks. | |
+| [bigquery-datasets](outputs.tf#L16) | BigQuery datasets. | |
+| [demo_commands](outputs.tf#L26) | Demo commands. Relevant only if Composer is deployed. | |
+| [df_template](outputs.tf#L49) | Dataflow template image and template details. | |
+| [gcs-buckets](outputs.tf#L58) | GCS buckets. | |
+| [kms_keys](outputs.tf#L71) | Cloud MKS keys. | |
+| [projects](outputs.tf#L76) | GCP Projects informations. | |
+| [vpc_network](outputs.tf#L102) | VPC network. | |
+| [vpc_subnet](outputs.tf#L111) | VPC subnetworks. | |
## TODOs
diff --git a/blueprints/data-solutions/data-platform-foundations/demo/README.md b/blueprints/data-solutions/data-platform-foundations/demo/README.md
index 97add086a..639549fca 100644
--- a/blueprints/data-solutions/data-platform-foundations/demo/README.md
+++ b/blueprints/data-solutions/data-platform-foundations/demo/README.md
@@ -23,10 +23,11 @@ Below you can find a description of each example:
## Running the demo
To run demo examples, please follow the following steps:
-- 01: copy sample data to the `drop off` Cloud Storage bucket impersonating the `load` service account.
-- 02: copy sample data structure definition in the `orchestration` Cloud Storage bucket impersonating the `orchestration` service account.
-- 03: copy the Cloud Composer DAG to the Cloud Composer Storage bucket impersonating the `orchestration` service account.
-- 04: Open the Cloud Composer Airflow UI and run the imported DAG.
-- 05: Run the BigQuery query to see results.
+- 01: Copy sample data to the `drop off` Cloud Storage bucket impersonating the `load` service account.
+- 02: Copy sample data structure definition in the `orchestration` Cloud Storage bucket impersonating the `orchestration` service account.
+- 03: Copy the Cloud Composer DAG to the Cloud Composer Storage bucket impersonating the `orchestration` service account.
+- 04: Build the Dataflow Flex template and image via a Cloud Build pipeline
+- 05: Open the Cloud Composer Airflow UI and run the imported DAG.
+- 06: Run the BigQuery query to see results.
You can find pre-computed commands in the `demo_commands` output variable of the deployed terraform [data pipeline](../).
diff --git a/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/.gitignore b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/.gitignore
new file mode 100644
index 000000000..68bc17f9f
--- /dev/null
+++ b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/.gitignore
@@ -0,0 +1,160 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
diff --git a/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/Dockerfile b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/Dockerfile
new file mode 100644
index 000000000..69c6d2eef
--- /dev/null
+++ b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/Dockerfile
@@ -0,0 +1,29 @@
+# Copyright 2023 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
+#
+# https://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.
+
+FROM gcr.io/dataflow-templates-base/python39-template-launcher-base
+
+ENV FLEX_TEMPLATE_PYTHON_REQUIREMENTS_FILE="/template/requirements.txt"
+ENV FLEX_TEMPLATE_PYTHON_PY_FILE="/template/csv2bq.py"
+
+COPY ./src/ /template
+
+RUN apt-get update \
+ && apt-get install -y libffi-dev git \
+ && rm -rf /var/lib/apt/lists/* \
+ && pip install --no-cache-dir --upgrade pip \
+ && pip install --no-cache-dir -r $FLEX_TEMPLATE_PYTHON_REQUIREMENTS_FILE \
+ && pip download --no-cache-dir --dest /tmp/dataflow-requirements-cache -r $FLEX_TEMPLATE_PYTHON_REQUIREMENTS_FILE
+
+ENV PIP_NO_DEPS=True
diff --git a/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/README.md b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/README.md
new file mode 100644
index 000000000..b052fab05
--- /dev/null
+++ b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/README.md
@@ -0,0 +1,122 @@
+## Pipeline summary
+This demo serves as a simple example of building and launching a Flex Template Dataflow pipeline. The code mainly focuses on reading a CSV file as input along with a JSON schema file as side input. The pipeline Parses both inputs and writes the data to the relevant BigQuery table while applying the schema passed from input.
+
+
+
+
+## Local development run
+
+For local development, the pipeline can be launched from the local machine for testing purposes using different runners depending on the scope of the test.
+
+### Using the Beam DirectRunner
+The below example uses the Beam DirectRunner. The use case for this runner is mainly for quick local tests on the development environment with low volume of data.
+
+```
+CSV_FILE=gs://[TEST-BUCKET]/customers.csv
+JSON_SCHEMA=gs://[TEST-BUCKET]/customers_schema.json
+OUTPUT_TABLE=[TEST-PROJ].[TEST-DATASET].customers
+PIPELINE_STAGIN_PATH="gs://[TEST-STAGING-BUCKET]"
+
+python src/csv2bq.py \
+--runner="DirectRunner" \
+--csv_file=$CSV_FILE \
+--json_schema=$JSON_SCHEMA \
+--output_table=$OUTPUT_TABLE \
+--temp_location=$PIPELINE_STAGIN_PATH/tmp
+```
+
+*Note:* All paths mentioned can be local paths or on GCS. For cloud resources referenced (GCS and BigQuery), make sure that the user launching the command is authenticated to GCP via `gcloud auth application-default login` and has the required access privileges to those resources.
+
+### Using the DataflowRunner with a local CLI launch
+
+The below example triggers the pipeline on Dataflow from your local development environment. The use case for this is for running local tests on larger volumes of test data and verifying that the pipeline runs well on Dataflow, before compiling it into a template.
+
+```
+PROJECT_ID=[TEST-PROJECT]
+REGION=[REGION]
+SUBNET=[SUBNET-NAME]
+DEV_SERVICE_ACCOUNT=[DEV-SA]
+
+PIPELINE_STAGIN_PATH="gs://[TEST-STAGING-BUCKET]"
+CSV_FILE=gs://[TEST-BUCKET]/customers.csv
+JSON_SCHEMA=gs://[TEST-BUCKET]/customers_schema.json
+OUTPUT_TABLE=[TEST-PROJ].[TEST-DATASET].customers
+
+python src/csv2bq.py \
+--runner="Dataflow" \
+--project=$PROJECT_ID \
+--region=$REGION \
+--csv_file=$CSV_FILE \
+--json_schema=$JSON_SCHEMA \
+--output_table=$OUTPUT_TABLE \
+--temp_location=$PIPELINE_STAGIN_PATH/tmp
+--staging_location=$PIPELINE_STAGIN_PATH/stage \
+--subnetwork="regions/$REGION/subnetworks/$SUBNET" \
+--impersonate_service_account=$DEV_SERVICE_ACCOUNT \
+--no_use_public_ips
+```
+
+In terms of resource access privilege, you can choose to impersonate another service account, which could be defined for development resource access. The authenticated user launching this pipeline will need to have the role `roles/iam.serviceAccountTokenCreator`. If you choose to launch the pipeline without service account impersonation, it will use the default compute service account assigned of the target project.
+
+## Dataflow Flex Template run
+
+For production, and as outline in the Data Platform demo, we build and launch the pipeline as a Flex Template, making it available for other cloud services(such as Apache Airflow) and users to trigger launch instances of it on demand.
+
+### Build launch
+
+Below is an example for triggering the Dataflow flex template build pipeline defined in `cloudbuild.yaml`. The Terraform output provides an example as well filled with the parameters values based on the generated resources in the data platform.
+
+```
+GCP_PROJECT="[ORCHESTRATION-PROJECT]"
+TEMPLATE_IMAGE="[REGION].pkg.dev/[ORCHESTRATION-PROJECT]/[REPOSITORY]/csv2bq:latest"
+TEMPLATE_PATH="gs://[DATAFLOW-TEMPLATE-BUCKEt]/csv2bq.json"
+STAGIN_PATH="gs://[ORCHESTRATION-STAGING-BUCKET]/build"
+LOG_PATH="gs://[ORCHESTRATION-LOGS-BUCKET]/logs"
+REGION="[REGION]"
+BUILD_SERVICE_ACCOUNT=orc-sa-df-build@[SERVICE_PROJECT_ID].iam.gserviceaccount.com
+
+gcloud builds submit \
+ --config=cloudbuild.yaml \
+ --project=$GCP_PROJECT \
+ --region=$REGION \
+ --gcs-log-dir=$LOG_PATH \
+ --gcs-source-staging-dir=$STAGIN_PATH \
+ --substitutions=_TEMPLATE_IMAGE=$TEMPLATE_IMAGE,_TEMPLATE_PATH=$TEMPLATE_PATH,_DOCKER_DIR="." \
+ --impersonate-service-account=$BUILD_SERVICE_ACCOUNT
+```
+
+**Note:** For the scope of the demo, the launch of this build is manual, but in production, this build would be launched via a configured cloud build trigger when new changes are merged into the code branch of the Dataflow template.
+
+### Dataflow Flex Template run
+
+After the build step succeeds. You can launch dataflow pipeline from CLI (outline in this example) or the API via Airflow's operator. For the use case of the data platform, the Dataflow pipeline would be launched via the orchestration service account, which is what the Airflow DAG is also using in the scope of this demo.
+
+**Note:** In the data platform demo, the launch of this Dataflow pipeline is handled by the airflow operator (DataflowStartFlexTemplateOperator).
+
+```
+#!/bin/bash
+
+PROJECT_ID=[LOAD-PROJECT]
+REGION=[REGION]
+ORCH_SERVICE_ACCOUNT=orchestrator@[SERVICE_PROJECT_ID].iam.gserviceaccount.com
+SUBNET=[SUBNET-NAME]
+
+PIPELINE_STAGIN_PATH="gs://[LOAD-STAGING-BUCKET]/build"
+CSV_FILE=gs://[DROP-ZONE-BUCKET]/customers.csv
+JSON_SCHEMA=gs://[ORCHESTRATION-BUCKET]/customers_schema.json
+OUTPUT_TABLE=[DESTINATION-PROJ].[DESTINATION-DATASET].customers
+TEMPLATE_PATH=gs://[ORCHESTRATION-DF-GCS]/csv2bq.json
+
+
+gcloud dataflow flex-template run "csv2bq-`date +%Y%m%d-%H%M%S`" \
+ --template-file-gcs-location $TEMPLATE_PATH \
+ --parameters temp_location="$PIPELINE_STAGIN_PATH/tmp" \
+ --parameters staging_location="$PIPELINE_STAGIN_PATH/stage" \
+ --parameters csv_file=$CSV_FILE \
+ --parameters json_schema=$JSON_SCHEMA\
+ --parameters output_table=$OUTPUT_TABLE \
+ --region $REGION \
+ --project $PROJECT_ID \
+ --subnetwork="regions/$REGION/subnetworks/$SUBNET" \
+ --service-account-email=$ORCH_SERVICE_ACCOUNT
+```
diff --git a/blueprints/cloud-operations/network-dashboard/versions.tf b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/cloudbuild.yaml
similarity index 54%
rename from blueprints/cloud-operations/network-dashboard/versions.tf
rename to blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/cloudbuild.yaml
index 3bdf23370..11354c2ed 100644
--- a/blueprints/cloud-operations/network-dashboard/versions.tf
+++ b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/cloudbuild.yaml
@@ -1,4 +1,4 @@
-# Copyright 2022 Google LLC
+# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,16 +12,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-terraform {
- required_version = ">= 1.3.1"
- required_providers {
- google = {
- source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
- }
- google-beta = {
- source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
- }
- }
-}
+steps:
+- name: gcr.io/cloud-builders/gcloud
+ id: "Build docker image"
+ args: ['builds', 'submit', '--tag', '$_TEMPLATE_IMAGE', '.']
+ dir: '$_DOCKER_DIR'
+ waitFor: ['-']
+- name: gcr.io/cloud-builders/gcloud
+ id: "Build template"
+ args: ['dataflow',
+ 'flex-template',
+ 'build',
+ '$_TEMPLATE_PATH',
+ '--image=$_TEMPLATE_IMAGE',
+ '--sdk-language=PYTHON'
+ ]
+ waitFor: ['Build docker image']
diff --git a/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/src/csv2bq.py b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/src/csv2bq.py
new file mode 100644
index 000000000..0f8ad1275
--- /dev/null
+++ b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/src/csv2bq.py
@@ -0,0 +1,79 @@
+# Copyright 2023 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
+#
+# https://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.
+
+import apache_beam as beam
+from apache_beam.io import ReadFromText, Read, WriteToBigQuery, BigQueryDisposition
+from apache_beam.options.pipeline_options import PipelineOptions, SetupOptions
+from apache_beam.io.filesystems import FileSystems
+import json
+import argparse
+
+
+class ParseRow(beam.DoFn):
+ """
+ Splits a given csv row by a seperator, validates fields and returns a dict
+ structure compatible with the BigQuery transform
+ """
+
+ def process(self, element: str, table_fields: list, delimiter: str):
+ split_row = element.split(delimiter)
+ parsed_row = {}
+
+ for i, field in enumerate(table_fields['BigQuery Schema']):
+ parsed_row[field['name']] = split_row[i]
+
+ yield parsed_row
+
+def run(argv=None, save_main_session=True):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--csv_file',
+ type=str,
+ required=True,
+ help='Path to the CSV file')
+ parser.add_argument('--json_schema',
+ type=str,
+ required=True,
+ help='Path to the JSON schema')
+ parser.add_argument('--output_table',
+ type=str,
+ required=True,
+ help='BigQuery path for the output table')
+
+ args, pipeline_args = parser.parse_known_args(argv)
+ pipeline_options = PipelineOptions(pipeline_args)
+ pipeline_options.view_as(
+ SetupOptions).save_main_session = save_main_session
+
+ with beam.Pipeline(options=pipeline_options) as p:
+
+ def get_table_schema(table_path, table_schema):
+ return {'fields': table_schema['BigQuery Schema']}
+
+ csv_input = p | 'Read CSV' >> ReadFromText(args.csv_file)
+ schema_input = p | 'Load Schema' >> beam.Create(
+ json.loads(FileSystems.open(args.json_schema).read()))
+
+ table_fields = beam.pvalue.AsDict(schema_input)
+ parsed = csv_input | 'Parse and validate rows' >> beam.ParDo(
+ ParseRow(), table_fields, ',')
+
+ parsed | 'Write to BigQuery' >> WriteToBigQuery(
+ args.output_table,
+ schema=get_table_schema,
+ create_disposition=BigQueryDisposition.CREATE_IF_NEEDED,
+ write_disposition=BigQueryDisposition.WRITE_TRUNCATE,
+ schema_side_inputs=(table_fields, ))
+
+if __name__ == "__main__":
+ run()
diff --git a/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/src/requirements.txt b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/src/requirements.txt
new file mode 100644
index 000000000..21c569a0d
--- /dev/null
+++ b/blueprints/data-solutions/data-platform-foundations/demo/dataflow-csv2bq/src/requirements.txt
@@ -0,0 +1 @@
+apache-beam==2.44.0
diff --git a/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_dc_tags_flex.py b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_dc_tags_flex.py
new file mode 100644
index 000000000..f911e335e
--- /dev/null
+++ b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_dc_tags_flex.py
@@ -0,0 +1,461 @@
+# 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
+#
+# https://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.
+
+# --------------------------------------------------------------------------------
+# Load The Dependencies
+# --------------------------------------------------------------------------------
+
+import datetime
+import json
+import os
+import time
+
+from airflow import models
+from airflow.operators import dummy
+from airflow.providers.google.cloud.operators.dataflow import DataflowStartFlexTemplateOperator
+from airflow.providers.google.cloud.operators.bigquery import BigQueryInsertJobOperator, BigQueryUpsertTableOperator, BigQueryUpdateTableSchemaOperator
+from airflow.utils.task_group import TaskGroup
+
+# --------------------------------------------------------------------------------
+# Set variables - Needed for the DEMO
+# --------------------------------------------------------------------------------
+BQ_LOCATION = os.environ.get("BQ_LOCATION")
+DATA_CAT_TAGS = json.loads(os.environ.get("DATA_CAT_TAGS"))
+DWH_LAND_PRJ = os.environ.get("DWH_LAND_PRJ")
+DWH_LAND_BQ_DATASET = os.environ.get("DWH_LAND_BQ_DATASET")
+DWH_LAND_GCS = os.environ.get("DWH_LAND_GCS")
+DWH_CURATED_PRJ = os.environ.get("DWH_CURATED_PRJ")
+DWH_CURATED_BQ_DATASET = os.environ.get("DWH_CURATED_BQ_DATASET")
+DWH_CURATED_GCS = os.environ.get("DWH_CURATED_GCS")
+DWH_CONFIDENTIAL_PRJ = os.environ.get("DWH_CONFIDENTIAL_PRJ")
+DWH_CONFIDENTIAL_BQ_DATASET = os.environ.get("DWH_CONFIDENTIAL_BQ_DATASET")
+DWH_CONFIDENTIAL_GCS = os.environ.get("DWH_CONFIDENTIAL_GCS")
+DWH_PLG_PRJ = os.environ.get("DWH_PLG_PRJ")
+DWH_PLG_BQ_DATASET = os.environ.get("DWH_PLG_BQ_DATASET")
+DWH_PLG_GCS = os.environ.get("DWH_PLG_GCS")
+GCP_REGION = os.environ.get("GCP_REGION")
+DRP_PRJ = os.environ.get("DRP_PRJ")
+DRP_BQ = os.environ.get("DRP_BQ")
+DRP_GCS = os.environ.get("DRP_GCS")
+DRP_PS = os.environ.get("DRP_PS")
+LOD_PRJ = os.environ.get("LOD_PRJ")
+LOD_GCS_STAGING = os.environ.get("LOD_GCS_STAGING")
+LOD_NET_VPC = os.environ.get("LOD_NET_VPC")
+LOD_NET_SUBNET = os.environ.get("LOD_NET_SUBNET")
+LOD_SA_DF = os.environ.get("LOD_SA_DF")
+ORC_PRJ = os.environ.get("ORC_PRJ")
+ORC_GCS = os.environ.get("ORC_GCS")
+ORC_GCS_TMP_DF = os.environ.get("ORC_GCS_TMP_DF")
+TRF_PRJ = os.environ.get("TRF_PRJ")
+TRF_GCS_STAGING = os.environ.get("TRF_GCS_STAGING")
+TRF_NET_VPC = os.environ.get("TRF_NET_VPC")
+TRF_NET_SUBNET = os.environ.get("TRF_NET_SUBNET")
+TRF_SA_DF = os.environ.get("TRF_SA_DF")
+TRF_SA_BQ = os.environ.get("TRF_SA_BQ")
+DF_KMS_KEY = os.environ.get("DF_KMS_KEY", "")
+DF_REGION = os.environ.get("GCP_REGION")
+DF_ZONE = os.environ.get("GCP_REGION") + "-b"
+
+# --------------------------------------------------------------------------------
+# Set default arguments
+# --------------------------------------------------------------------------------
+
+# If you are running Airflow in more than one time zone
+# see https://airflow.apache.org/docs/apache-airflow/stable/timezone.html
+# for best practices
+yesterday = datetime.datetime.now() - datetime.timedelta(days=1)
+
+default_args = {
+ 'owner': 'airflow',
+ 'start_date': yesterday,
+ 'depends_on_past': False,
+ 'email': [''],
+ 'email_on_failure': False,
+ 'email_on_retry': False,
+ 'retries': 1,
+ 'retry_delay': datetime.timedelta(minutes=5),
+}
+
+dataflow_environment = {
+ 'serviceAccountEmail': LOD_SA_DF,
+ 'workerZone': DF_ZONE,
+ 'stagingLocation': f'{LOD_GCS_STAGING}/staging',
+ 'tempLocation': f'{LOD_GCS_STAGING}/tmp',
+ 'subnetwork': LOD_NET_SUBNET,
+ 'kmsKeyName': DF_KMS_KEY,
+ 'ipConfiguration': 'WORKER_IP_PRIVATE'
+}
+
+# --------------------------------------------------------------------------------
+# Main DAG
+# --------------------------------------------------------------------------------
+
+with models.DAG('data_pipeline_dc_tags_dag_flex',
+ default_args=default_args,
+ schedule_interval=None) as dag:
+ start = dummy.DummyOperator(task_id='start', trigger_rule='all_success')
+
+ end = dummy.DummyOperator(task_id='end', trigger_rule='all_success')
+
+ # Bigquery Tables created here for demo porpuse.
+ # Consider a dedicated pipeline or tool for a real life scenario.
+ with TaskGroup('upsert_table') as upsert_table:
+ upsert_table_customers = BigQueryUpsertTableOperator(
+ task_id="upsert_table_customers",
+ project_id=DWH_LAND_PRJ,
+ dataset_id=DWH_LAND_BQ_DATASET,
+ impersonation_chain=[TRF_SA_DF],
+ table_resource={
+ "tableReference": {
+ "tableId": "customers"
+ },
+ },
+ )
+
+ upsert_table_purchases = BigQueryUpsertTableOperator(
+ task_id="upsert_table_purchases",
+ project_id=DWH_LAND_PRJ,
+ dataset_id=DWH_LAND_BQ_DATASET,
+ impersonation_chain=[TRF_SA_BQ],
+ table_resource={"tableReference": {
+ "tableId": "purchases"
+ }},
+ )
+
+ upsert_table_customer_purchase_curated = BigQueryUpsertTableOperator(
+ task_id="upsert_table_customer_purchase_curated",
+ project_id=DWH_CURATED_PRJ,
+ dataset_id=DWH_CURATED_BQ_DATASET,
+ impersonation_chain=[TRF_SA_BQ],
+ table_resource={
+ "tableReference": {
+ "tableId": "customer_purchase"
+ }
+ },
+ )
+
+ upsert_table_customer_purchase_confidential = BigQueryUpsertTableOperator(
+ task_id="upsert_table_customer_purchase_confidential",
+ project_id=DWH_CONFIDENTIAL_PRJ,
+ dataset_id=DWH_CONFIDENTIAL_BQ_DATASET,
+ impersonation_chain=[TRF_SA_BQ],
+ table_resource={
+ "tableReference": {
+ "tableId": "customer_purchase"
+ }
+ },
+ )
+
+ # Bigquery Tables schema defined here for demo porpuse.
+ # Consider a dedicated pipeline or tool for a real life scenario.
+ with TaskGroup('update_schema_table') as update_schema_table:
+ update_table_schema_customers = BigQueryUpdateTableSchemaOperator(
+ task_id="update_table_schema_customers",
+ project_id=DWH_LAND_PRJ,
+ dataset_id=DWH_LAND_BQ_DATASET,
+ table_id="customers",
+ impersonation_chain=[TRF_SA_BQ],
+ include_policy_tags=True,
+ schema_fields_updates=[{
+ "mode": "REQUIRED",
+ "name": "id",
+ "type": "INTEGER",
+ "description": "ID"
+ }, {
+ "mode": "REQUIRED",
+ "name": "name",
+ "type": "STRING",
+ "description": "Name",
+ "policyTags": {
+ "names": [DATA_CAT_TAGS.get('2_Private', None)]
+ }
+ }, {
+ "mode": "REQUIRED",
+ "name": "surname",
+ "type": "STRING",
+ "description": "Surname",
+ "policyTags": {
+ "names": [DATA_CAT_TAGS.get('2_Private', None)]
+ }
+ }, {
+ "mode": "REQUIRED",
+ "name": "timestamp",
+ "type": "TIMESTAMP",
+ "description": "Timestamp"
+ }])
+
+ update_table_schema_purchases = BigQueryUpdateTableSchemaOperator(
+ task_id="update_table_schema_purchases",
+ project_id=DWH_LAND_PRJ,
+ dataset_id=DWH_LAND_BQ_DATASET,
+ table_id="purchases",
+ impersonation_chain=[TRF_SA_BQ],
+ include_policy_tags=True,
+ schema_fields_updates=[{
+ "mode": "REQUIRED",
+ "name": "id",
+ "type": "INTEGER",
+ "description": "ID"
+ }, {
+ "mode": "REQUIRED",
+ "name": "customer_id",
+ "type": "INTEGER",
+ "description": "ID"
+ }, {
+ "mode": "REQUIRED",
+ "name": "item",
+ "type": "STRING",
+ "description": "Item Name"
+ }, {
+ "mode": "REQUIRED",
+ "name": "price",
+ "type": "FLOAT",
+ "description": "Item Price"
+ }, {
+ "mode": "REQUIRED",
+ "name": "timestamp",
+ "type": "TIMESTAMP",
+ "description": "Timestamp"
+ }])
+
+ update_table_schema_customer_purchase_curated = BigQueryUpdateTableSchemaOperator(
+ task_id="update_table_schema_customer_purchase_curated",
+ project_id=DWH_CURATED_PRJ,
+ dataset_id=DWH_CURATED_BQ_DATASET,
+ table_id="customer_purchase",
+ impersonation_chain=[TRF_SA_BQ],
+ include_policy_tags=True,
+ schema_fields_updates=[{
+ "mode": "REQUIRED",
+ "name": "customer_id",
+ "type": "INTEGER",
+ "description": "ID"
+ }, {
+ "mode": "REQUIRED",
+ "name": "purchase_id",
+ "type": "INTEGER",
+ "description": "ID"
+ }, {
+ "mode": "REQUIRED",
+ "name": "name",
+ "type": "STRING",
+ "description": "Name",
+ "policyTags": {
+ "names": [DATA_CAT_TAGS.get('2_Private', None)]
+ }
+ }, {
+ "mode": "REQUIRED",
+ "name": "surname",
+ "type": "STRING",
+ "description": "Surname",
+ "policyTags": {
+ "names": [DATA_CAT_TAGS.get('2_Private', None)]
+ }
+ }, {
+ "mode": "REQUIRED",
+ "name": "item",
+ "type": "STRING",
+ "description": "Item Name"
+ }, {
+ "mode": "REQUIRED",
+ "name": "price",
+ "type": "FLOAT",
+ "description": "Item Price"
+ }, {
+ "mode": "REQUIRED",
+ "name": "timestamp",
+ "type": "TIMESTAMP",
+ "description": "Timestamp"
+ }])
+
+ update_table_schema_customer_purchase_confidential = BigQueryUpdateTableSchemaOperator(
+ task_id="update_table_schema_customer_purchase_confidential",
+ project_id=DWH_CONFIDENTIAL_PRJ,
+ dataset_id=DWH_CONFIDENTIAL_BQ_DATASET,
+ table_id="customer_purchase",
+ impersonation_chain=[TRF_SA_BQ],
+ include_policy_tags=True,
+ schema_fields_updates=[{
+ "mode": "REQUIRED",
+ "name": "customer_id",
+ "type": "INTEGER",
+ "description": "ID"
+ }, {
+ "mode": "REQUIRED",
+ "name": "purchase_id",
+ "type": "INTEGER",
+ "description": "ID"
+ }, {
+ "mode": "REQUIRED",
+ "name": "name",
+ "type": "STRING",
+ "description": "Name",
+ "policyTags": {
+ "names": [DATA_CAT_TAGS.get('2_Private', None)]
+ }
+ }, {
+ "mode": "REQUIRED",
+ "name": "surname",
+ "type": "STRING",
+ "description": "Surname",
+ "policyTags": {
+ "names": [DATA_CAT_TAGS.get('2_Private', None)]
+ }
+ }, {
+ "mode": "REQUIRED",
+ "name": "item",
+ "type": "STRING",
+ "description": "Item Name"
+ }, {
+ "mode": "REQUIRED",
+ "name": "price",
+ "type": "FLOAT",
+ "description": "Item Price"
+ }, {
+ "mode": "REQUIRED",
+ "name": "timestamp",
+ "type": "TIMESTAMP",
+ "description": "Timestamp"
+ }])
+
+ customers_import = DataflowStartFlexTemplateOperator(
+ task_id='dataflow_customers_import',
+ project_id=LOD_PRJ,
+ location=DF_REGION,
+ body={
+ 'launchParameter': {
+ 'jobName': f'dataflow-customers-import-{round(time.time())}',
+ 'containerSpecGcsPath': f'{ORC_GCS_TMP_DF}/csv2bq.json',
+ 'environment': {
+ 'serviceAccountEmail': LOD_SA_DF,
+ 'workerZone': DF_ZONE,
+ 'stagingLocation': f'{LOD_GCS_STAGING}/staging',
+ 'tempLocation': f'{LOD_GCS_STAGING}/tmp',
+ 'subnetwork': LOD_NET_SUBNET,
+ 'kmsKeyName': DF_KMS_KEY,
+ 'ipConfiguration': 'WORKER_IP_PRIVATE'
+ },
+ 'parameters': {
+ 'csv_file':
+ f'{DRP_GCS}/customers.csv',
+ 'json_schema':
+ f'{ORC_GCS}/customers_schema.json',
+ 'output_table':
+ f'{DWH_LAND_PRJ}:{DWH_LAND_BQ_DATASET}.customers',
+ }
+ }
+ })
+
+ purchases_import = DataflowStartFlexTemplateOperator(
+ task_id='dataflow_purchases_import',
+ project_id=LOD_PRJ,
+ location=DF_REGION,
+ body={
+ 'launchParameter': {
+ 'jobName': f'dataflow-purchases-import-{round(time.time())}',
+ 'containerSpecGcsPath': f'{ORC_GCS_TMP_DF}/csv2bq.json',
+ 'environment': {
+ 'serviceAccountEmail': LOD_SA_DF,
+ 'workerZone': DF_ZONE,
+ 'stagingLocation': f'{LOD_GCS_STAGING}/staging',
+ 'tempLocation': f'{LOD_GCS_STAGING}/tmp',
+ 'subnetwork': LOD_NET_SUBNET,
+ 'kmsKeyName': DF_KMS_KEY,
+ 'ipConfiguration': 'WORKER_IP_PRIVATE'
+ },
+ 'parameters': {
+ 'csv_file':
+ f'{DRP_GCS}/purchases.csv',
+ 'json_schema':
+ f'{ORC_GCS}/purchases_schema.json',
+ 'output_table':
+ f'{DWH_LAND_PRJ}:{DWH_LAND_BQ_DATASET}.purchases',
+ }
+ }
+ })
+
+ join_customer_purchase = BigQueryInsertJobOperator(
+ task_id='bq_join_customer_purchase',
+ gcp_conn_id='bigquery_default',
+ project_id=TRF_PRJ,
+ location=BQ_LOCATION,
+ configuration={
+ 'jobType': 'QUERY',
+ 'query': {
+ 'query':
+ """SELECT
+ c.id as customer_id,
+ p.id as purchase_id,
+ c.name as name,
+ c.surname as surname,
+ p.item as item,
+ p.price as price,
+ p.timestamp as timestamp
+ FROM `{dwh_0_prj}.{dwh_0_dataset}.customers` c
+ JOIN `{dwh_0_prj}.{dwh_0_dataset}.purchases` p ON c.id = p.customer_id
+ """.format(
+ dwh_0_prj=DWH_LAND_PRJ,
+ dwh_0_dataset=DWH_LAND_BQ_DATASET,
+ ),
+ 'destinationTable': {
+ 'projectId': DWH_CURATED_PRJ,
+ 'datasetId': DWH_CURATED_BQ_DATASET,
+ 'tableId': 'customer_purchase'
+ },
+ 'writeDisposition':
+ 'WRITE_TRUNCATE',
+ "useLegacySql":
+ False
+ }
+ },
+ impersonation_chain=[TRF_SA_BQ])
+
+ confidential_customer_purchase = BigQueryInsertJobOperator(
+ task_id='bq_confidential_customer_purchase',
+ gcp_conn_id='bigquery_default',
+ project_id=TRF_PRJ,
+ location=BQ_LOCATION,
+ configuration={
+ 'jobType': 'QUERY',
+ 'query': {
+ 'query':
+ """SELECT
+ customer_id,
+ purchase_id,
+ name,
+ surname,
+ item,
+ price,
+ timestamp
+ FROM `{dwh_cur_prj}.{dwh_cur_dataset}.customer_purchase`
+ """.format(
+ dwh_cur_prj=DWH_CURATED_PRJ,
+ dwh_cur_dataset=DWH_CURATED_BQ_DATASET,
+ ),
+ 'destinationTable': {
+ 'projectId': DWH_CONFIDENTIAL_PRJ,
+ 'datasetId': DWH_CONFIDENTIAL_BQ_DATASET,
+ 'tableId': 'customer_purchase'
+ },
+ 'writeDisposition':
+ 'WRITE_TRUNCATE',
+ "useLegacySql":
+ False
+ }
+ },
+ impersonation_chain=[TRF_SA_BQ])
+
+start >> upsert_table >> update_schema_table >> [
+ customers_import, purchases_import
+] >> join_customer_purchase >> confidential_customer_purchase >> end
diff --git a/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_flex.py b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_flex.py
new file mode 100644
index 000000000..34ff10ccd
--- /dev/null
+++ b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_flex.py
@@ -0,0 +1,225 @@
+# 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
+#
+# https://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.
+
+# --------------------------------------------------------------------------------
+# Load The Dependencies
+# --------------------------------------------------------------------------------
+
+import datetime
+import json
+import os
+import time
+
+from airflow import models
+from airflow.providers.google.cloud.operators.dataflow import DataflowStartFlexTemplateOperator
+from airflow.operators import dummy
+from airflow.providers.google.cloud.operators.bigquery import BigQueryInsertJobOperator
+
+# --------------------------------------------------------------------------------
+# Set variables - Needed for the DEMO
+# --------------------------------------------------------------------------------
+BQ_LOCATION = os.environ.get("BQ_LOCATION")
+DATA_CAT_TAGS = json.loads(os.environ.get("DATA_CAT_TAGS"))
+DWH_LAND_PRJ = os.environ.get("DWH_LAND_PRJ")
+DWH_LAND_BQ_DATASET = os.environ.get("DWH_LAND_BQ_DATASET")
+DWH_LAND_GCS = os.environ.get("DWH_LAND_GCS")
+DWH_CURATED_PRJ = os.environ.get("DWH_CURATED_PRJ")
+DWH_CURATED_BQ_DATASET = os.environ.get("DWH_CURATED_BQ_DATASET")
+DWH_CURATED_GCS = os.environ.get("DWH_CURATED_GCS")
+DWH_CONFIDENTIAL_PRJ = os.environ.get("DWH_CONFIDENTIAL_PRJ")
+DWH_CONFIDENTIAL_BQ_DATASET = os.environ.get("DWH_CONFIDENTIAL_BQ_DATASET")
+DWH_CONFIDENTIAL_GCS = os.environ.get("DWH_CONFIDENTIAL_GCS")
+DWH_PLG_PRJ = os.environ.get("DWH_PLG_PRJ")
+DWH_PLG_BQ_DATASET = os.environ.get("DWH_PLG_BQ_DATASET")
+DWH_PLG_GCS = os.environ.get("DWH_PLG_GCS")
+GCP_REGION = os.environ.get("GCP_REGION")
+DRP_PRJ = os.environ.get("DRP_PRJ")
+DRP_BQ = os.environ.get("DRP_BQ")
+DRP_GCS = os.environ.get("DRP_GCS")
+DRP_PS = os.environ.get("DRP_PS")
+LOD_PRJ = os.environ.get("LOD_PRJ")
+LOD_GCS_STAGING = os.environ.get("LOD_GCS_STAGING")
+LOD_NET_VPC = os.environ.get("LOD_NET_VPC")
+LOD_NET_SUBNET = os.environ.get("LOD_NET_SUBNET")
+LOD_SA_DF = os.environ.get("LOD_SA_DF")
+ORC_PRJ = os.environ.get("ORC_PRJ")
+ORC_GCS = os.environ.get("ORC_GCS")
+ORC_GCS_TMP_DF = os.environ.get("ORC_GCS_TMP_DF")
+TRF_PRJ = os.environ.get("TRF_PRJ")
+TRF_GCS_STAGING = os.environ.get("TRF_GCS_STAGING")
+TRF_NET_VPC = os.environ.get("TRF_NET_VPC")
+TRF_NET_SUBNET = os.environ.get("TRF_NET_SUBNET")
+TRF_SA_DF = os.environ.get("TRF_SA_DF")
+TRF_SA_BQ = os.environ.get("TRF_SA_BQ")
+DF_KMS_KEY = os.environ.get("DF_KMS_KEY", "")
+DF_REGION = os.environ.get("GCP_REGION")
+DF_ZONE = os.environ.get("GCP_REGION") + "-b"
+
+# --------------------------------------------------------------------------------
+# Set default arguments
+# --------------------------------------------------------------------------------
+
+# If you are running Airflow in more than one time zone
+# see https://airflow.apache.org/docs/apache-airflow/stable/timezone.html
+# for best practices
+yesterday = datetime.datetime.now() - datetime.timedelta(days=1)
+
+default_args = {
+ 'owner': 'airflow',
+ 'start_date': yesterday,
+ 'depends_on_past': False,
+ 'email': [''],
+ 'email_on_failure': False,
+ 'email_on_retry': False,
+ 'retries': 1,
+ 'retry_delay': datetime.timedelta(minutes=5),
+}
+
+dataflow_environment = {
+ 'serviceAccountEmail': LOD_SA_DF,
+ 'workerZone': DF_ZONE,
+ 'stagingLocation': f'{LOD_GCS_STAGING}/staging',
+ 'tempLocation': f'{LOD_GCS_STAGING}/tmp',
+ 'subnetwork': LOD_NET_SUBNET,
+ 'kmsKeyName': DF_KMS_KEY,
+ 'ipConfiguration': 'WORKER_IP_PRIVATE'
+}
+
+# --------------------------------------------------------------------------------
+# Main DAG
+# --------------------------------------------------------------------------------
+
+with models.DAG('data_pipeline_dag_flex',
+ default_args=default_args,
+ schedule_interval=None) as dag:
+
+ start = dummy.DummyOperator(task_id='start', trigger_rule='all_success')
+
+ end = dummy.DummyOperator(task_id='end', trigger_rule='all_success')
+
+ # Bigquery Tables automatically created for demo purposes.
+ # Consider a dedicated pipeline or tool for a real life scenario.
+ customers_import = DataflowStartFlexTemplateOperator(
+ task_id='dataflow_customers_import',
+ project_id=LOD_PRJ,
+ location=DF_REGION,
+ body={
+ 'launchParameter': {
+ 'jobName': f'dataflow-customers-import-{round(time.time())}',
+ 'containerSpecGcsPath': f'{ORC_GCS_TMP_DF}/csv2bq.json',
+ 'environment': dataflow_environment,
+ 'parameters': {
+ 'csv_file':
+ f'{DRP_GCS}/customers.csv',
+ 'json_schema':
+ f'{ORC_GCS}/customers_schema.json',
+ 'output_table':
+ f'{DWH_LAND_PRJ}:{DWH_LAND_BQ_DATASET}.customers',
+ }
+ }
+ })
+
+ purchases_import = DataflowStartFlexTemplateOperator(
+ task_id='dataflow_purchases_import',
+ project_id=LOD_PRJ,
+ location=DF_REGION,
+ body={
+ 'launchParameter': {
+ 'jobName': f'dataflow-purchases-import-{round(time.time())}',
+ 'containerSpecGcsPath': f'{ORC_GCS_TMP_DF}/csv2bq.json',
+ 'environment': dataflow_environment,
+ 'parameters': {
+ 'csv_file':
+ f'{DRP_GCS}/purchases.csv',
+ 'json_schema':
+ f'{ORC_GCS}/purchases_schema.json',
+ 'output_table':
+ f'{DWH_LAND_PRJ}:{DWH_LAND_BQ_DATASET}.purchases',
+ }
+ }
+ })
+
+ join_customer_purchase = BigQueryInsertJobOperator(
+ task_id='bq_join_customer_purchase',
+ gcp_conn_id='bigquery_default',
+ project_id=TRF_PRJ,
+ location=BQ_LOCATION,
+ configuration={
+ 'jobType': 'QUERY',
+ 'query': {
+ 'query':
+ """SELECT
+ c.id as customer_id,
+ p.id as purchase_id,
+ p.item as item,
+ p.price as price,
+ p.timestamp as timestamp
+ FROM `{dwh_0_prj}.{dwh_0_dataset}.customers` c
+ JOIN `{dwh_0_prj}.{dwh_0_dataset}.purchases` p ON c.id = p.customer_id
+ """.format(
+ dwh_0_prj=DWH_LAND_PRJ,
+ dwh_0_dataset=DWH_LAND_BQ_DATASET,
+ ),
+ 'destinationTable': {
+ 'projectId': DWH_CURATED_PRJ,
+ 'datasetId': DWH_CURATED_BQ_DATASET,
+ 'tableId': 'customer_purchase'
+ },
+ 'writeDisposition':
+ 'WRITE_TRUNCATE',
+ "useLegacySql":
+ False
+ }
+ },
+ impersonation_chain=[TRF_SA_BQ])
+
+ confidential_customer_purchase = BigQueryInsertJobOperator(
+ task_id='bq_confidential_customer_purchase',
+ gcp_conn_id='bigquery_default',
+ project_id=TRF_PRJ,
+ location=BQ_LOCATION,
+ configuration={
+ 'jobType': 'QUERY',
+ 'query': {
+ 'query':
+ """SELECT
+ c.id as customer_id,
+ p.id as purchase_id,
+ c.name as name,
+ c.surname as surname,
+ p.item as item,
+ p.price as price,
+ p.timestamp as timestamp
+ FROM `{dwh_0_prj}.{dwh_0_dataset}.customers` c
+ JOIN `{dwh_0_prj}.{dwh_0_dataset}.purchases` p ON c.id = p.customer_id
+ """.format(
+ dwh_0_prj=DWH_LAND_PRJ,
+ dwh_0_dataset=DWH_LAND_BQ_DATASET,
+ ),
+ 'destinationTable': {
+ 'projectId': DWH_CONFIDENTIAL_PRJ,
+ 'datasetId': DWH_CONFIDENTIAL_BQ_DATASET,
+ 'tableId': 'customer_purchase'
+ },
+ 'writeDisposition':
+ 'WRITE_TRUNCATE',
+ "useLegacySql":
+ False
+ }
+ },
+ impersonation_chain=[TRF_SA_BQ])
+
+ start >> [
+ customers_import, purchases_import
+ ] >> join_customer_purchase >> confidential_customer_purchase >> end
diff --git a/blueprints/data-solutions/data-platform-foundations/images/df_demo_pipeline.png b/blueprints/data-solutions/data-platform-foundations/images/df_demo_pipeline.png
new file mode 100644
index 000000000..541532b41
Binary files /dev/null and b/blueprints/data-solutions/data-platform-foundations/images/df_demo_pipeline.png differ
diff --git a/blueprints/data-solutions/data-platform-foundations/images/overview_diagram.png b/blueprints/data-solutions/data-platform-foundations/images/overview_diagram.png
index 642c81c2f..073ec870c 100644
Binary files a/blueprints/data-solutions/data-platform-foundations/images/overview_diagram.png and b/blueprints/data-solutions/data-platform-foundations/images/overview_diagram.png differ
diff --git a/blueprints/data-solutions/data-platform-foundations/outputs.tf b/blueprints/data-solutions/data-platform-foundations/outputs.tf
index b941776cb..ae853da00 100644
--- a/blueprints/data-solutions/data-platform-foundations/outputs.tf
+++ b/blueprints/data-solutions/data-platform-foundations/outputs.tf
@@ -13,7 +13,6 @@
# limitations under the License.
# tfdoc:file:description Output variables.
-
output "bigquery-datasets" {
description = "BigQuery datasets."
value = {
@@ -21,30 +20,47 @@ output "bigquery-datasets" {
dwh-landing-bq-0 = module.dwh-lnd-bq-0.dataset_id,
dwh-curated-bq-0 = module.dwh-cur-bq-0.dataset_id,
dwh-confidential-bq-0 = module.dwh-conf-bq-0.dataset_id,
- dwh-plg-bq-0 = module.dwh-plg-bq-0.dataset_id,
}
}
output "demo_commands" {
- description = "Demo commands."
+ description = "Demo commands. Relevant only if Composer is deployed."
value = {
01 = "gsutil -i ${module.drop-sa-cs-0.email} cp demo/data/*.csv gs://${module.drop-cs-0.name}"
- 02 = "gsutil -i ${module.orch-sa-cmp-0.email} cp demo/data/*.j* gs://${module.orch-cs-0.name}"
- 03 = "gsutil -i ${module.orch-sa-cmp-0.email} cp demo/*.py ${google_composer_environment.orch-cmp-0.config[0].dag_gcs_prefix}/"
- 04 = "Open ${google_composer_environment.orch-cmp-0.config.0.airflow_uri} and run uploaded DAG."
- 05 = <string | ✓ | |
+| [prefix](variables.tf#L32) | Prefix used for resource names. | string | ✓ | |
+| [project_id](variables.tf#L50) | Project id, references existing project if `project_create` is null. | string | ✓ | |
| [location](variables.tf#L16) | The location where resources will be deployed. | string | | "EU" |
-| [project_create](variables.tf#L31) | Provide values if project creation is needed, uses existing project if null. Parent format: folders/folder_id or organizations/org_id. | object({…}) | | null |
-| [region](variables.tf#L45) | The region where resources will be deployed. | string | | "europe-west1" |
-| [vpc_config](variables.tf#L61) | Parameters to create a VPC. | object({…}) | | {…} |
+| [network_config](variables.tf#L22) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | object({…}) | | null |
+| [project_create](variables.tf#L41) | Provide values if project creation is needed, uses existing project if null. Parent format: folders/folder_id or organizations/org_id. | object({…}) | | null |
+| [region](variables.tf#L55) | The region where resources will be deployed. | string | | "europe-west1" |
## Outputs
diff --git a/blueprints/data-solutions/data-playground/main.tf b/blueprints/data-solutions/data-playground/main.tf
index ff079d5eb..548bee37d 100644
--- a/blueprints/data-solutions/data-playground/main.tf
+++ b/blueprints/data-solutions/data-playground/main.tf
@@ -17,6 +17,39 @@
###############################################################################
locals {
service_encryption_keys = var.service_encryption_keys
+ shared_vpc_project = try(var.network_config.host_project, null)
+
+ subnet = (
+ local.use_shared_vpc
+ ? var.network_config.subnet_self_link
+ : values(module.vpc.0.subnet_self_links)[0]
+ )
+ vpc = (
+ local.use_shared_vpc
+ ? var.network_config.network_self_link
+ : module.vpc.0.self_link
+ )
+ use_shared_vpc = var.network_config != null
+
+ shared_vpc_bindings = {
+ "roles/compute.networkUser" = [
+ "robot-df", "notebooks"
+ ]
+ }
+
+ shared_vpc_role_members = {
+ robot-df = "serviceAccount:${module.project.service_accounts.robots.dataflow}"
+ notebooks = "serviceAccount:${module.project.service_accounts.robots.notebooks}"
+ }
+
+ # reassemble in a format suitable for for_each
+ shared_vpc_bindings_map = {
+ for binding in flatten([
+ for role, members in local.shared_vpc_bindings : [
+ for member in members : { role = role, member = member }
+ ]
+ ]) : "${binding.role}-${binding.member}" => binding
+ }
}
module "project" {
@@ -27,6 +60,7 @@ module "project" {
project_create = var.project_create != null
prefix = var.project_create == null ? null : var.prefix
services = [
+ "aiplatform.googleapis.com",
"bigquery.googleapis.com",
"bigquerystorage.googleapis.com",
"bigqueryreservation.googleapis.com",
@@ -42,17 +76,26 @@ module "project" {
"storage.googleapis.com",
"storage-component.googleapis.com"
]
+
+ shared_vpc_service_config = local.shared_vpc_project == null ? null : {
+ attach = true
+ host_project = local.shared_vpc_project
+ }
+
org_policies = {
# "constraints/compute.requireOsLogin" = {
# enforce = false
# }
- # Example of applying a project wide policy, mainly useful for Composer
+ # Example of applying a project wide policy, mainly useful for Composer 1
}
service_encryption_key_ids = {
compute = [try(local.service_encryption_keys.compute, null)]
bq = [try(local.service_encryption_keys.bq, null)]
storage = [try(local.service_encryption_keys.storage, null)]
}
+ service_config = {
+ disable_on_destroy = false, disable_dependent_services = false
+ }
}
###############################################################################
@@ -61,11 +104,12 @@ module "project" {
module "vpc" {
source = "../../../modules/net-vpc"
+ count = local.use_shared_vpc ? 0 : 1
project_id = module.project.project_id
name = "${var.prefix}-vpc"
subnets = [
{
- ip_cidr_range = var.vpc_config.ip_cidr_range
+ ip_cidr_range = "10.0.0.0/20"
name = "${var.prefix}-subnet"
region = var.region
}
@@ -74,10 +118,11 @@ module "vpc" {
module "vpc-firewall" {
source = "../../../modules/net-vpc-firewall"
+ count = local.use_shared_vpc ? 0 : 1
project_id = module.project.project_id
- network = module.vpc.name
+ network = module.vpc.0.name
default_rules_config = {
- admin_ranges = [var.vpc_config.ip_cidr_range]
+ admin_ranges = ["10.0.0.0/20"]
}
ingress_rules = {
#TODO Remove and rely on 'ssh' tag once terraform-provider-google/issues/9273 is fixed
@@ -92,12 +137,21 @@ module "vpc-firewall" {
module "cloudnat" {
source = "../../../modules/net-cloudnat"
+ count = local.use_shared_vpc ? 0 : 1
project_id = module.project.project_id
name = "${var.prefix}-default"
region = var.region
- router_network = module.vpc.name
+ router_network = module.vpc.0.name
}
+resource "google_project_iam_member" "shared_vpc" {
+ count = local.use_shared_vpc ? 1 : 0
+ project = var.network_config.host_project
+ role = "roles/compute.networkUser"
+ member = "serviceAccount:${module.project.service_accounts.robots.notebooks}"
+}
+
+
###############################################################################
# Storage #
###############################################################################
@@ -121,8 +175,6 @@ module "dataset" {
###############################################################################
# Vertex AI Notebook #
###############################################################################
-# TODO: Add encryption_key to Vertex AI notebooks as well
-# TODO: Add shared VPC support
module "service-account-notebook" {
source = "../../../modules/iam-service-account"
@@ -160,11 +212,19 @@ resource "google_notebooks_instance" "playground" {
no_public_ip = true
no_proxy_access = false
- network = module.vpc.network.id
- subnet = module.vpc.subnets[format("%s/%s", var.region, "${var.prefix}-subnet")].id
+ network = local.vpc
+ subnet = local.subnet
service_account = module.service-account-notebook.email
+ # Remove once terraform-provider-google/issues/9164 is fixed
+ lifecycle {
+ ignore_changes = [disk_encryption, kms_key]
+ }
+
#TODO Uncomment once terraform-provider-google/issues/9273 is fixed
# tags = ["ssh"]
+ depends_on = [
+ google_project_iam_member.shared_vpc,
+ ]
}
diff --git a/blueprints/data-solutions/data-playground/outputs.tf b/blueprints/data-solutions/data-playground/outputs.tf
index 4b80c311c..35f2efeb1 100644
--- a/blueprints/data-solutions/data-playground/outputs.tf
+++ b/blueprints/data-solutions/data-playground/outputs.tf
@@ -37,5 +37,5 @@ output "project" {
output "vpc" {
description = "VPC Network."
- value = module.vpc.name
+ value = local.vpc
}
diff --git a/blueprints/data-solutions/data-playground/variables.tf b/blueprints/data-solutions/data-playground/variables.tf
index 173540673..3bd0ca65b 100644
--- a/blueprints/data-solutions/data-playground/variables.tf
+++ b/blueprints/data-solutions/data-playground/variables.tf
@@ -19,6 +19,16 @@ variable "location" {
default = "EU"
}
+variable "network_config" {
+ description = "Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values."
+ type = object({
+ host_project = string
+ network_self_link = string
+ subnet_self_link = string
+ })
+ default = null
+}
+
variable "prefix" {
description = "Prefix used for resource names."
type = string
@@ -57,13 +67,3 @@ variable "service_encryption_keys" { # service encription key
})
default = null
}
-
-variable "vpc_config" {
- description = "Parameters to create a VPC."
- type = object({
- ip_cidr_range = string
- })
- default = {
- ip_cidr_range = "10.0.0.0/20"
- }
-}
diff --git a/blueprints/data-solutions/data-playground/versions.tf b/blueprints/data-solutions/data-playground/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/data-solutions/data-playground/versions.tf
+++ b/blueprints/data-solutions/data-playground/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/data-solutions/gcs-to-bq-with-least-privileges/versions.tf b/blueprints/data-solutions/gcs-to-bq-with-least-privileges/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/data-solutions/gcs-to-bq-with-least-privileges/versions.tf
+++ b/blueprints/data-solutions/gcs-to-bq-with-least-privileges/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/data-solutions/shielded-folder/README.md b/blueprints/data-solutions/shielded-folder/README.md
new file mode 100644
index 000000000..aaf67e6ac
--- /dev/null
+++ b/blueprints/data-solutions/shielded-folder/README.md
@@ -0,0 +1,180 @@
+# Shielded folder
+
+This blueprint implements an opinionated folder configuration according to GCP best practices. Configurations set at the folder level would be beneficial to host workloads inheriting constraints from the folder they belong to.
+
+In this blueprint, a folder will be created setting following features:
+
+- Organizational policies
+- Hierarchical firewall rules
+- Cloud KMS
+- VPC-SC
+
+Within the folder, the following projects will be created:
+
+- 'audit-logs' where Audit Logs sink will be created
+- 'sec-core' where Cloud KMS and Cloud Secret manager will be configured
+
+The following diagram is a high-level reference of the resources created and managed here:
+
+
+
+## Design overview and choices
+
+Despite its simplicity, this blueprint implements the basics of a design that we've seen working well for various customers.
+
+The approach adapts to different high-level requirements:
+
+- IAM roles inheritance
+- Organizational policies
+- Audit log sink
+- VPC Service Control
+- Cloud KMS
+
+## Project structure
+
+The Shielded Folder blueprint is designed to rely on several projects:
+
+- `audit-log`: to host Audit logging buckets and Audit log sync to GCS, BigQuery or PubSub
+- `sec-core`: to host security-related resources such as Cloud KMS and Cloud Secrets Manager
+
+This separation into projects allows adhering to the least-privilege principle by using project-level roles.
+
+## User groups
+
+User groups provide a stable frame of reference that allows decoupling the final set of permissions from the stage where entities and resources are created, and their IAM bindings are defined.
+
+We use groups to control access to resources:
+
+- `data-engineers`: They handle and run workloads on the `workload` subfolder. They have editor access to all resources in the `workload` folder in order to troubleshoot possible issues within the workload. This team can also impersonate any service account in the workload folder.
+- `data-security`: They handle security configurations for the shielded folder. They have owner access to the `audit-log` and `sec-core` projects.
+
+## Encryption
+
+The blueprint supports the configuration of an instance of Cloud KMS to handle encryption on the resources. The encryption is disabled by default, but you can enable it by configuring the `enable_features.encryption` variable.
+
+The script will create keys to encrypt log sink buckets/datasets/topics in the specified regions. Configuring the `kms_keys` variable, you can create additional KMS keys needed by your workload.
+
+## Customizations
+
+### Organization policy
+
+You can configure the Organization policies enforced on the folder editing yaml files in the [org-policies](./data/org-policies/) folder. An opinionated list of policies that we suggest enforcing is listed.
+
+Some additional Organization policy constraints you may want to evaluate adding:
+
+- `constraints/gcp.resourceLocations`: to define the locations where location-based GCP resources can be created.
+- `constraints/gcp.restrictCmekCryptoKeyProjects`: to define which projects may be used to supply Customer-Managed Encryption Keys (CMEK) when creating resources.
+
+### VPC Service Control
+
+VPC Service Control is configured to have a Perimeter containing all projects within the folder. Additional projects you may add to the folder won't be automatically added to the perimeter, and a new `terraform apply` is needed to add the project to the perimeter.
+
+The VPC SC configuration is set to dry-run mode, but switching to enforced mode is a simple operation involving modifying a few lines of code highlighted by ad-hoc comments.
+
+Access level rules are not defined. Before moving the configuration to enforced mode, configure access policies to continue accessing resources from outside of the perimeter.
+
+An access level based on the network range you are using to reach the console (e.g. Proxy IP, Internet connection, ...) is suggested. Example:
+
+```tfvars
+vpc_sc_access_levels = {
+ users = {
+ conditions = [
+ { members = ["user:user1@example.com"] }
+ ]
+ }
+}
+```
+
+Alternatively, you can configure an access level based on the identity that needs to reach resources from outside the perimeter.
+
+```tfvars
+vpc_sc_access_levels = {
+ users = {
+ conditions = [
+ { ip_subnetworks = ["101.101.101.0/24"] }
+ ]
+ }
+}
+```
+
+## How to run this script
+
+To deploy this blueprint in your GCP organization, you will need
+
+- a folder or organization where resources will be created
+- a billing account that will be associated with the new projects
+
+The Shielded Folder blueprint is meant to be executed by a Service Account (or a regular user) having this minimal set of permission:
+
+- Billing account
+ - `roles/billing.user`
+- Folder level
+ - `roles/resourcemanager.folderAdmin`
+ - `roles/resourcemanager.projectCreator`
+
+The shielded Folfer blueprint assumes [groups described](#user-groups) are created in your GCP organization.
+
+### Variable configuration PIPPO
+
+There are several sets of variables you will need to fill in:
+
+```tfvars
+access_policy_config = {
+ access_policy_create = {
+ parent = "organizations/1234567890123"
+ title = "ShieldedMVP"
+ }
+}
+folder_config = {
+ folder_create = {
+ display_name = "ShieldedMVP"
+ parent = "organizations/1234567890123"
+ }
+}
+organization = {
+ domain = "example.com"
+ id = "1122334455"
+}
+prefix = "prefix"
+project_config = {
+ billing_account_id = "123456-123456-123456"
+}
+```
+
+### Deploying the blueprint
+
+Once the configuration is complete, run the project factory by running
+
+```bash
+terraform init
+terraform apply
+```
+
+
+## Variables
+
+| name | description | type | required | default |
+|---|---|:---:|:---:|:---:|
+| [access_policy_config](variables.tf#L17) | Provide 'access_policy_create' values if a folder scoped Access Policy creation is needed, uses existing 'policy_name' otherwise. Parent is in 'organizations/123456' format. Policy will be created scoped to the folder. | object({…}) | ✓ | |
+| [folder_config](variables.tf#L49) | Provide 'folder_create' values if folder creation is needed, uses existing 'folder_id' otherwise. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | ✓ | |
+| [organization](variables.tf#L128) | Organization details. | object({…}) | ✓ | |
+| [prefix](variables.tf#L136) | Prefix used for resources that need unique names. | string | ✓ | |
+| [project_config](variables.tf#L141) | Provide 'billing_account_id' value if project creation is needed, uses existing 'project_ids' if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | ✓ | |
+| [data_dir](variables.tf#L29) | Relative path for the folder storing configuration data. | string | | "data" |
+| [enable_features](variables.tf#L35) | Flag to enable features on the solution. | object({…}) | | {…} |
+| [groups](variables.tf#L65) | User groups. | object({…}) | | {} |
+| [kms_keys](variables.tf#L75) | KMS keys to create, keyed by name. | map(object({…})) | | {} |
+| [log_locations](variables.tf#L86) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} |
+| [log_sinks](variables.tf#L103) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} |
+| [vpc_sc_access_levels](variables.tf#L161) | VPC SC access level definitions. | map(object({…})) | | {} |
+| [vpc_sc_egress_policies](variables.tf#L190) | VPC SC egress policy defnitions. | map(object({…})) | | {} |
+| [vpc_sc_ingress_policies](variables.tf#L210) | VPC SC ingress policy defnitions. | map(object({…})) | | {} |
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| [folders](outputs.tf#L15) | Folders id. | |
+| [folders_sink_writer_identities](outputs.tf#L23) | Folders id. | |
+
+
diff --git a/blueprints/data-solutions/shielded-folder/data/firewall-policies/cidrs.yaml b/blueprints/data-solutions/shielded-folder/data/firewall-policies/cidrs.yaml
new file mode 100644
index 000000000..90dabfb6a
--- /dev/null
+++ b/blueprints/data-solutions/shielded-folder/data/firewall-policies/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
\ No newline at end of file
diff --git a/blueprints/data-solutions/shielded-folder/data/firewall-policies/hierarchical-policy-rules.yaml b/blueprints/data-solutions/shielded-folder/data/firewall-policies/hierarchical-policy-rules.yaml
new file mode 100644
index 000000000..6a3b31335
--- /dev/null
+++ b/blueprints/data-solutions/shielded-folder/data/firewall-policies/hierarchical-policy-rules.yaml
@@ -0,0 +1,50 @@
+# 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
+
\ No newline at end of file
diff --git a/fast/stages/01-resman/data/org-policies/compute.yaml b/blueprints/data-solutions/shielded-folder/data/org-policies/compute.yaml
similarity index 100%
rename from fast/stages/01-resman/data/org-policies/compute.yaml
rename to blueprints/data-solutions/shielded-folder/data/org-policies/compute.yaml
diff --git a/fast/stages/01-resman/data/org-policies/iam.yaml b/blueprints/data-solutions/shielded-folder/data/org-policies/iam.yaml
similarity index 100%
rename from fast/stages/01-resman/data/org-policies/iam.yaml
rename to blueprints/data-solutions/shielded-folder/data/org-policies/iam.yaml
diff --git a/fast/stages/01-resman/data/org-policies/serverless.yaml b/blueprints/data-solutions/shielded-folder/data/org-policies/serverless.yaml
similarity index 100%
rename from fast/stages/01-resman/data/org-policies/serverless.yaml
rename to blueprints/data-solutions/shielded-folder/data/org-policies/serverless.yaml
diff --git a/fast/stages/01-resman/data/org-policies/sql.yaml b/blueprints/data-solutions/shielded-folder/data/org-policies/sql.yaml
similarity index 100%
rename from fast/stages/01-resman/data/org-policies/sql.yaml
rename to blueprints/data-solutions/shielded-folder/data/org-policies/sql.yaml
diff --git a/fast/stages/01-resman/data/org-policies/storage.yaml b/blueprints/data-solutions/shielded-folder/data/org-policies/storage.yaml
similarity index 100%
rename from fast/stages/01-resman/data/org-policies/storage.yaml
rename to blueprints/data-solutions/shielded-folder/data/org-policies/storage.yaml
diff --git a/blueprints/data-solutions/shielded-folder/data/vpc-sc/accessible-services.yaml b/blueprints/data-solutions/shielded-folder/data/vpc-sc/accessible-services.yaml
new file mode 100644
index 000000000..2107d2ff1
--- /dev/null
+++ b/blueprints/data-solutions/shielded-folder/data/vpc-sc/accessible-services.yaml
@@ -0,0 +1,119 @@
+# skip boilerplate check
+
+- accessapproval.googleapis.com
+- adsdatahub.googleapis.com
+- aiplatform.googleapis.com
+- alloydb.googleapis.com
+- alpha-documentai.googleapis.com
+- analyticshub.googleapis.com
+- apigee.googleapis.com
+- apigeeconnect.googleapis.com
+- artifactregistry.googleapis.com
+- assuredworkloads.googleapis.com
+- automl.googleapis.com
+- baremetalsolution.googleapis.com
+- batch.googleapis.com
+- beyondcorp.googleapis.com
+- bigquery.googleapis.com
+- bigquerydatapolicy.googleapis.com
+- bigquerydatatransfer.googleapis.com
+- bigquerymigration.googleapis.com
+- bigqueryreservation.googleapis.com
+- bigtable.googleapis.com
+- binaryauthorization.googleapis.com
+- cloudasset.googleapis.com
+- cloudbuild.googleapis.com
+- clouddebugger.googleapis.com
+- clouderrorreporting.googleapis.com
+- cloudfunctions.googleapis.com
+- cloudkms.googleapis.com
+- cloudprofiler.googleapis.com
+- cloudresourcemanager.googleapis.com
+- cloudsearch.googleapis.com
+- cloudtrace.googleapis.com
+- composer.googleapis.com
+- compute.googleapis.com
+- connectgateway.googleapis.com
+- contactcenterinsights.googleapis.com
+- container.googleapis.com
+- containeranalysis.googleapis.com
+- containerfilesystem.googleapis.com
+- containerregistry.googleapis.com
+- containerthreatdetection.googleapis.com
+- contentwarehouse.googleapis.com
+- datacatalog.googleapis.com
+- dataflow.googleapis.com
+- datafusion.googleapis.com
+- datalineage.googleapis.com
+- datamigration.googleapis.com
+- datapipelines.googleapis.com
+- dataplex.googleapis.com
+- dataproc.googleapis.com
+- datastream.googleapis.com
+- dialogflow.googleapis.com
+- dlp.googleapis.com
+- dns.googleapis.com
+- documentai.googleapis.com
+- domains.googleapis.com
+- essentialcontacts.googleapis.com
+- eventarc.googleapis.com
+- file.googleapis.com
+- firebaseappcheck.googleapis.com
+- firebaserules.googleapis.com
+- firestore.googleapis.com
+- gameservices.googleapis.com
+- gkebackup.googleapis.com
+- gkeconnect.googleapis.com
+- gkehub.googleapis.com
+- gkemulticloud.googleapis.com
+- healthcare.googleapis.com
+- iam.googleapis.com
+- iamcredentials.googleapis.com
+- iaptunnel.googleapis.com
+- ids.googleapis.com
+- integrations.googleapis.com
+- language.googleapis.com
+- lifesciences.googleapis.com
+- logging.googleapis.com
+- managedidentities.googleapis.com
+- memcache.googleapis.com
+- meshca.googleapis.com
+- metastore.googleapis.com
+- ml.googleapis.com
+- monitoring.googleapis.com
+- networkconnectivity.googleapis.com
+- networkmanagement.googleapis.com
+- networksecurity.googleapis.com
+- networkservices.googleapis.com
+- notebooks.googleapis.com
+- opsconfigmonitoring.googleapis.com
+- osconfig.googleapis.com
+- oslogin.googleapis.com
+- policytroubleshooter.googleapis.com
+- privateca.googleapis.com
+- pubsub.googleapis.com
+- pubsublite.googleapis.com
+- recaptchaenterprise.googleapis.com
+- recommender.googleapis.com
+- redis.googleapis.com
+- retail.googleapis.com
+- run.googleapis.com
+- secretmanager.googleapis.com
+- servicecontrol.googleapis.com
+- servicedirectory.googleapis.com
+- spanner.googleapis.com
+- speakerid.googleapis.com
+- speech.googleapis.com
+- sqladmin.googleapis.com
+- storage.googleapis.com
+- storagetransfer.googleapis.com
+- texttospeech.googleapis.com
+- tpu.googleapis.com
+- trafficdirector.googleapis.com
+- transcoder.googleapis.com
+- translate.googleapis.com
+- videointelligence.googleapis.com
+- vision.googleapis.com
+- visionai.googleapis.com
+- vpcaccess.googleapis.com
+- workstations.googleapis.com
\ No newline at end of file
diff --git a/blueprints/data-solutions/shielded-folder/data/vpc-sc/restricted-services.yaml b/blueprints/data-solutions/shielded-folder/data/vpc-sc/restricted-services.yaml
new file mode 100644
index 000000000..2107d2ff1
--- /dev/null
+++ b/blueprints/data-solutions/shielded-folder/data/vpc-sc/restricted-services.yaml
@@ -0,0 +1,119 @@
+# skip boilerplate check
+
+- accessapproval.googleapis.com
+- adsdatahub.googleapis.com
+- aiplatform.googleapis.com
+- alloydb.googleapis.com
+- alpha-documentai.googleapis.com
+- analyticshub.googleapis.com
+- apigee.googleapis.com
+- apigeeconnect.googleapis.com
+- artifactregistry.googleapis.com
+- assuredworkloads.googleapis.com
+- automl.googleapis.com
+- baremetalsolution.googleapis.com
+- batch.googleapis.com
+- beyondcorp.googleapis.com
+- bigquery.googleapis.com
+- bigquerydatapolicy.googleapis.com
+- bigquerydatatransfer.googleapis.com
+- bigquerymigration.googleapis.com
+- bigqueryreservation.googleapis.com
+- bigtable.googleapis.com
+- binaryauthorization.googleapis.com
+- cloudasset.googleapis.com
+- cloudbuild.googleapis.com
+- clouddebugger.googleapis.com
+- clouderrorreporting.googleapis.com
+- cloudfunctions.googleapis.com
+- cloudkms.googleapis.com
+- cloudprofiler.googleapis.com
+- cloudresourcemanager.googleapis.com
+- cloudsearch.googleapis.com
+- cloudtrace.googleapis.com
+- composer.googleapis.com
+- compute.googleapis.com
+- connectgateway.googleapis.com
+- contactcenterinsights.googleapis.com
+- container.googleapis.com
+- containeranalysis.googleapis.com
+- containerfilesystem.googleapis.com
+- containerregistry.googleapis.com
+- containerthreatdetection.googleapis.com
+- contentwarehouse.googleapis.com
+- datacatalog.googleapis.com
+- dataflow.googleapis.com
+- datafusion.googleapis.com
+- datalineage.googleapis.com
+- datamigration.googleapis.com
+- datapipelines.googleapis.com
+- dataplex.googleapis.com
+- dataproc.googleapis.com
+- datastream.googleapis.com
+- dialogflow.googleapis.com
+- dlp.googleapis.com
+- dns.googleapis.com
+- documentai.googleapis.com
+- domains.googleapis.com
+- essentialcontacts.googleapis.com
+- eventarc.googleapis.com
+- file.googleapis.com
+- firebaseappcheck.googleapis.com
+- firebaserules.googleapis.com
+- firestore.googleapis.com
+- gameservices.googleapis.com
+- gkebackup.googleapis.com
+- gkeconnect.googleapis.com
+- gkehub.googleapis.com
+- gkemulticloud.googleapis.com
+- healthcare.googleapis.com
+- iam.googleapis.com
+- iamcredentials.googleapis.com
+- iaptunnel.googleapis.com
+- ids.googleapis.com
+- integrations.googleapis.com
+- language.googleapis.com
+- lifesciences.googleapis.com
+- logging.googleapis.com
+- managedidentities.googleapis.com
+- memcache.googleapis.com
+- meshca.googleapis.com
+- metastore.googleapis.com
+- ml.googleapis.com
+- monitoring.googleapis.com
+- networkconnectivity.googleapis.com
+- networkmanagement.googleapis.com
+- networksecurity.googleapis.com
+- networkservices.googleapis.com
+- notebooks.googleapis.com
+- opsconfigmonitoring.googleapis.com
+- osconfig.googleapis.com
+- oslogin.googleapis.com
+- policytroubleshooter.googleapis.com
+- privateca.googleapis.com
+- pubsub.googleapis.com
+- pubsublite.googleapis.com
+- recaptchaenterprise.googleapis.com
+- recommender.googleapis.com
+- redis.googleapis.com
+- retail.googleapis.com
+- run.googleapis.com
+- secretmanager.googleapis.com
+- servicecontrol.googleapis.com
+- servicedirectory.googleapis.com
+- spanner.googleapis.com
+- speakerid.googleapis.com
+- speech.googleapis.com
+- sqladmin.googleapis.com
+- storage.googleapis.com
+- storagetransfer.googleapis.com
+- texttospeech.googleapis.com
+- tpu.googleapis.com
+- trafficdirector.googleapis.com
+- transcoder.googleapis.com
+- translate.googleapis.com
+- videointelligence.googleapis.com
+- vision.googleapis.com
+- visionai.googleapis.com
+- vpcaccess.googleapis.com
+- workstations.googleapis.com
\ No newline at end of file
diff --git a/blueprints/data-solutions/shielded-folder/images/overview_diagram.png b/blueprints/data-solutions/shielded-folder/images/overview_diagram.png
new file mode 100644
index 000000000..abc8d56e7
Binary files /dev/null and b/blueprints/data-solutions/shielded-folder/images/overview_diagram.png differ
diff --git a/blueprints/data-solutions/shielded-folder/kms.tf b/blueprints/data-solutions/shielded-folder/kms.tf
new file mode 100644
index 000000000..7a3b42b53
--- /dev/null
+++ b/blueprints/data-solutions/shielded-folder/kms.tf
@@ -0,0 +1,102 @@
+/**
+ * Copyright 2023 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 Security project, Cloud KMS and Secret Manager resources.
+
+locals {
+ kms_locations = distinct(flatten([
+ for k, v in var.kms_keys : v.locations
+ ]))
+ kms_locations_keys = {
+ for loc in local.kms_locations : loc => {
+ for k, v in var.kms_keys : k => v if contains(v.locations, loc)
+ }
+ }
+
+ kms_log_locations = distinct(flatten([
+ for k, v in local.kms_log_sink_keys : compact(v.locations)
+ ]))
+
+ # Log sink keys
+ kms_log_sink_keys = {
+ "storage" = {
+ labels = {}
+ locations = [var.log_locations.storage]
+ rotation_period = "7776000s"
+ }
+ "bq" = {
+ labels = {}
+ locations = [var.log_locations.bq]
+ rotation_period = "7776000s"
+ }
+ "pubsub" = {
+ labels = {}
+ locations = [var.log_locations.pubsub]
+ rotation_period = "7776000s"
+ }
+ }
+ kms_log_locations_keys = {
+ for loc in local.kms_log_locations : loc => {
+ for k, v in local.kms_log_sink_keys : k => v if contains(v.locations, loc)
+ }
+ }
+}
+
+module "sec-project" {
+ count = var.enable_features.encryption ? 1 : 0
+ source = "../../../modules/project"
+ name = var.project_config.project_ids["sec-core"]
+ parent = module.folder.id
+ billing_account = var.project_config.billing_account_id
+ project_create = var.project_config.billing_account_id != null && var.enable_features.encryption
+ prefix = var.project_config.billing_account_id == null ? null : var.prefix
+ group_iam = {
+ (local.groups.workload-security) = [
+ "roles/editor"
+ ]
+ }
+ services = [
+ "cloudkms.googleapis.com",
+ "secretmanager.googleapis.com",
+ "stackdriver.googleapis.com"
+ ]
+}
+
+module "sec-kms" {
+ for_each = var.enable_features.encryption ? toset(local.kms_locations) : toset([])
+ source = "../../../modules/kms"
+ project_id = module.sec-project[0].project_id
+ keyring = {
+ location = each.key
+ name = "${each.key}"
+ }
+ # rename to `key_iam` to switch to authoritative bindings
+ key_iam_additive = {
+ for k, v in local.kms_locations_keys[each.key] : k => v.iam
+ }
+ keys = local.kms_locations_keys[each.key]
+}
+
+module "log-kms" {
+ for_each = var.enable_features.encryption ? toset(local.kms_log_locations) : toset([])
+ source = "../../../modules/kms"
+ project_id = module.sec-project[0].project_id
+ keyring = {
+ location = each.key
+ name = "${each.key}"
+ }
+ keys = local.kms_log_locations_keys[each.key]
+}
diff --git a/blueprints/data-solutions/shielded-folder/log-export.tf b/blueprints/data-solutions/shielded-folder/log-export.tf
new file mode 100644
index 000000000..9eff32d20
--- /dev/null
+++ b/blueprints/data-solutions/shielded-folder/log-export.tf
@@ -0,0 +1,107 @@
+/**
+ * Copyright 2023 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 Audit log project and sink.
+
+locals {
+ gcs_storage_class = (
+ length(split("-", var.log_locations.storage)) < 2
+ ? "MULTI_REGIONAL"
+ : "REGIONAL"
+ )
+ log_types = toset([for k, v in var.log_sinks : v.type])
+
+ _log_keys = var.enable_features.encryption ? {
+ bq = var.enable_features.log_sink ? ["projects/${module.sec-project.0.project_id}/locations/${var.log_locations.bq}/keyRings/${var.log_locations.bq}/cryptoKeys/bq"] : null
+ pubsub = var.enable_features.log_sink ? ["projects/${module.sec-project.0.project_id}/locations/${var.log_locations.pubsub}/keyRings/${var.log_locations.pubsub}/cryptoKeys/pubsub"] : null
+ storage = var.enable_features.log_sink ? ["projects/${module.sec-project.0.project_id}/locations/${var.log_locations.storage}/keyRings/${var.log_locations.storage}/cryptoKeys/storage"] : null
+ } : {}
+
+ log_keys = {
+ for service, key in local._log_keys : service => key if key != null
+ }
+}
+
+module "log-export-project" {
+ count = var.enable_features.log_sink ? 1 : 0
+ source = "../../../modules/project"
+ name = var.project_config.project_ids["audit-logs"]
+ parent = module.folder.id
+ billing_account = var.project_config.billing_account_id
+ project_create = var.project_config.billing_account_id != null
+ prefix = var.project_config.billing_account_id == null ? null : var.prefix
+ group_iam = {
+ (local.groups.workload-security) = [
+ "roles/editor"
+ ]
+ }
+ iam = {
+ # "roles/owner" = [module.automation-tf-bootstrap-sa.iam_email]
+ }
+ services = [
+ "bigquery.googleapis.com",
+ "pubsub.googleapis.com",
+ "storage.googleapis.com",
+ "stackdriver.googleapis.com"
+ ]
+ service_encryption_key_ids = var.enable_features.encryption ? local.log_keys : {}
+
+ depends_on = [
+ module.log-kms
+ ]
+}
+
+# one log export per type, with conditionals to skip those not needed
+
+module "log-export-dataset" {
+ source = "../../../modules/bigquery-dataset"
+ count = var.enable_features.log_sink && contains(local.log_types, "bigquery") ? 1 : 0
+ project_id = module.log-export-project[0].project_id
+ id = "${var.prefix}_audit_export"
+ friendly_name = "Audit logs export."
+ location = replace(var.log_locations.bq, "europe", "EU")
+ encryption_key = var.enable_features.encryption ? module.log-kms[var.log_locations.bq].keys["bq"].id : false
+}
+
+module "log-export-gcs" {
+ source = "../../../modules/gcs"
+ count = var.enable_features.log_sink && contains(local.log_types, "storage") ? 1 : 0
+ project_id = module.log-export-project[0].project_id
+ name = "audit-logs"
+ prefix = var.prefix
+ location = replace(var.log_locations.storage, "europe", "EU")
+ storage_class = local.gcs_storage_class
+ encryption_key = var.enable_features.encryption ? module.log-kms[var.log_locations.storage].keys["storage"].id : null
+}
+
+module "log-export-logbucket" {
+ source = "../../../modules/logging-bucket"
+ for_each = var.enable_features.log_sink ? toset([for k, v in var.log_sinks : k if v.type == "logging"]) : []
+ parent_type = "project"
+ parent = module.log-export-project[0].project_id
+ id = "audit-logs-${each.key}"
+ location = var.log_locations.logging
+ #TODO check if logging bucket support encryption.
+}
+
+module "log-export-pubsub" {
+ source = "../../../modules/pubsub"
+ for_each = toset([for k, v in var.log_sinks : k if v.type == "pubsub" && var.enable_features.log_sink])
+ project_id = module.log-export-project[0].project_id
+ name = "audit-logs-${each.key}"
+ regions = [var.log_locations.pubsub]
+ kms_key = var.enable_features.encryption ? module.log-kms[var.log_locations.pubsub].keys["pubsub"].id : null
+}
diff --git a/blueprints/data-solutions/shielded-folder/main.tf b/blueprints/data-solutions/shielded-folder/main.tf
new file mode 100644
index 000000000..52fa0db2e
--- /dev/null
+++ b/blueprints/data-solutions/shielded-folder/main.tf
@@ -0,0 +1,141 @@
+# Copyright 2023 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
+#
+# https://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 Folder resources.
+
+locals {
+ # Create Log sink ingress policies
+ _sink_ingress_policies = var.enable_features.log_sink ? {
+ log_sink = {
+ from = {
+ access_levels = ["*"]
+ identities = values(module.folder.sink_writer_identities)
+ }
+ to = {
+ resources = ["projects/${module.log-export-project.0.number}"]
+ operations = [{ service_name = "*" }]
+ } }
+ } : null
+
+ _vpc_sc_vpc_accessible_services = var.data_dir != null ? yamldecode(
+ file("${var.data_dir}/vpc-sc/restricted-services.yaml")
+ ) : null
+ _vpc_sc_restricted_services = var.data_dir != null ? yamldecode(
+ file("${var.data_dir}/vpc-sc/restricted-services.yaml")
+ ) : null
+
+ access_policy_create = var.access_policy_config.access_policy_create != null ? {
+ parent = "organizations/${var.organization.id}"
+ title = "shielded-folder"
+ scopes = [module.folder.id]
+ } : null
+
+ groups = {
+ for k, v in var.groups : k => "${v}@${var.organization.domain}"
+ }
+ groups_iam = {
+ for k, v in local.groups : k => "group:${v}"
+ }
+ group_iam = {
+ (local.groups.workload-engineers) = [
+ "roles/editor",
+ "roles/iam.serviceAccountTokenCreator"
+ ]
+ }
+
+ vpc_sc_resources = [
+ for k, v in data.google_projects.folder-projects.projects : format("projects/%s", v.number)
+ ]
+
+ log_sink_destinations = var.enable_features.log_sink ? merge(
+ # use the same dataset for all sinks with `bigquery` as destination
+ { for k, v in var.log_sinks : k => module.log-export-dataset.0 if v.type == "bigquery" },
+ # use the same gcs bucket for all sinks with `storage` as destination
+ { for k, v in var.log_sinks : k => module.log-export-gcs.0 if v.type == "storage" },
+ # use separate pubsub topics and logging buckets for sinks with
+ # destination `pubsub` and `logging`
+ module.log-export-pubsub,
+ module.log-export-logbucket
+ ) : null
+}
+
+module "folder" {
+ source = "../../../modules/folder"
+ folder_create = var.folder_config.folder_create != null
+ parent = try(var.folder_config.folder_create.parent, null)
+ name = try(var.folder_config.folder_create.display_name, null)
+ id = var.folder_config.folder_create != null ? null : var.folder_config.folder_id
+ group_iam = local.group_iam
+ org_policies_data_path = var.data_dir != null ? "${var.data_dir}/org-policies" : null
+ firewall_policy_factory = var.data_dir != null ? {
+ cidr_file = "${var.data_dir}/firewall-policies/cidrs.yaml"
+ policy_name = "${var.prefix}-fw-policy"
+ rules_file = "${var.data_dir}/firewall-policies/hierarchical-policy-rules.yaml"
+ } : null
+ logging_sinks = var.enable_features.log_sink ? {
+ for name, attrs in var.log_sinks : name => {
+ bq_partitioned_table = attrs.type == "bigquery"
+ destination = local.log_sink_destinations[name].id
+ filter = attrs.filter
+ type = attrs.type
+ }
+ } : null
+}
+
+module "folder-workload" {
+ source = "../../../modules/folder"
+ parent = module.folder.id
+ name = "${var.prefix}-workload"
+}
+
+
+#TODO VPCSC: Access levels
+data "google_projects" "folder-projects" {
+ filter = "parent.id:${split("/", module.folder.id)[1]}"
+
+ depends_on = [
+ module.sec-project,
+ module.log-export-project
+ ]
+}
+
+module "vpc-sc" {
+ count = var.enable_features.vpc_sc ? 1 : 0
+ source = "../../../modules/vpc-sc"
+ access_policy = try(var.access_policy_config.policy_name, null)
+ access_policy_create = local.access_policy_create
+ access_levels = var.vpc_sc_access_levels
+ egress_policies = var.vpc_sc_egress_policies
+ ingress_policies = merge(var.vpc_sc_ingress_policies, local._sink_ingress_policies)
+ service_perimeters_regular = {
+ shielded = {
+ # Move `spec` definition to `status` and comment `use_explicit_dry_run_spec` variable to enforce VPC-SC configuration
+ # Before enforing configuration check logs and create Access Level, Ingress/Egress policy as needed
+
+ status = null
+ spec = {
+ access_levels = keys(var.vpc_sc_access_levels)
+ resources = local.vpc_sc_resources
+ restricted_services = local._vpc_sc_restricted_services
+ egress_policies = keys(var.vpc_sc_egress_policies)
+ ingress_policies = keys(merge(var.vpc_sc_ingress_policies, local._sink_ingress_policies))
+ vpc_accessible_services = {
+ allowed_services = local._vpc_sc_vpc_accessible_services
+ enable_restriction = true
+ }
+ }
+ use_explicit_dry_run_spec = true
+ }
+ }
+}
diff --git a/blueprints/data-solutions/shielded-folder/outputs.tf b/blueprints/data-solutions/shielded-folder/outputs.tf
new file mode 100644
index 000000000..e1107fc61
--- /dev/null
+++ b/blueprints/data-solutions/shielded-folder/outputs.tf
@@ -0,0 +1,30 @@
+# Copyright 2023 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
+#
+# https://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.
+
+output "folders" {
+ description = "Folders id."
+ value = {
+ shielded-folder = module.folder.id
+ workload-folder = module.folder-workload.id
+ }
+}
+
+output "folders_sink_writer_identities" {
+ description = "Folders id."
+ value = {
+ shielded-folder = module.folder.sink_writer_identities
+ workload-folder = module.folder-workload.sink_writer_identities
+ }
+}
+
diff --git a/blueprints/data-solutions/shielded-folder/variables.tf b/blueprints/data-solutions/shielded-folder/variables.tf
new file mode 100644
index 000000000..a9ecbb241
--- /dev/null
+++ b/blueprints/data-solutions/shielded-folder/variables.tf
@@ -0,0 +1,229 @@
+# Copyright 2023 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
+#
+# https://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 Variables definition.
+
+variable "access_policy_config" {
+ description = "Provide 'access_policy_create' values if a folder scoped Access Policy creation is needed, uses existing 'policy_name' otherwise. Parent is in 'organizations/123456' format. Policy will be created scoped to the folder."
+ type = object({
+ policy_name = optional(string, null)
+ access_policy_create = optional(object({
+ parent = string
+ title = string
+ }), null)
+ })
+ nullable = false
+}
+
+variable "data_dir" {
+ description = "Relative path for the folder storing configuration data."
+ type = string
+ default = "data"
+}
+
+variable "enable_features" {
+ description = "Flag to enable features on the solution."
+ type = object({
+ encryption = optional(bool, false)
+ log_sink = optional(bool, true)
+ vpc_sc = optional(bool, true)
+ })
+ default = {
+ encryption = false
+ log_sink = true
+ vpc_sc = true
+ }
+}
+
+variable "folder_config" {
+ description = "Provide 'folder_create' values if folder creation is needed, uses existing 'folder_id' otherwise. Parent is in 'folders/nnn' or 'organizations/nnn' format."
+ type = object({
+ folder_id = optional(string, null)
+ folder_create = optional(object({
+ display_name = string
+ parent = string
+ }), null)
+ })
+ validation {
+ condition = var.folder_config.folder_id != null || var.folder_config.folder_create != null
+ error_message = "At least one attribute should be set."
+ }
+ nullable = false
+}
+
+variable "groups" {
+ description = "User groups."
+ type = object({
+ workload-engineers = optional(string, "gcp-data-engineers")
+ workload-security = optional(string, "gcp-data-security")
+ })
+ default = {}
+ nullable = false
+}
+
+variable "kms_keys" {
+ description = "KMS keys to create, keyed by name."
+ type = map(object({
+ iam = optional(map(list(string)), {})
+ labels = optional(map(string), {})
+ locations = optional(list(string), ["global", "europe", "europe-west1"])
+ rotation_period = optional(string, "7776000s")
+ }))
+ default = {}
+}
+
+variable "log_locations" {
+ description = "Optional locations for GCS, BigQuery, and logging buckets created here."
+ type = object({
+ bq = optional(string, "europe")
+ storage = optional(string, "europe")
+ logging = optional(string, "global")
+ pubsub = optional(string, "global")
+ })
+ default = {
+ bq = "europe"
+ storage = "europe"
+ logging = "global"
+ pubsub = null
+ }
+ nullable = false
+}
+
+variable "log_sinks" {
+ description = "Org-level log sinks, in name => {type, filter} format."
+ type = map(object({
+ filter = string
+ type = string
+ }))
+ default = {
+ audit-logs = {
+ filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\""
+ type = "bigquery"
+ }
+ vpc-sc = {
+ filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\""
+ type = "bigquery"
+ }
+ }
+ validation {
+ condition = alltrue([
+ for k, v in var.log_sinks :
+ contains(["bigquery", "logging", "pubsub", "storage"], v.type)
+ ])
+ error_message = "Type must be one of 'bigquery', 'logging', 'pubsub', 'storage'."
+ }
+}
+
+variable "organization" {
+ description = "Organization details."
+ type = object({
+ domain = string
+ id = string
+ })
+}
+
+variable "prefix" {
+ description = "Prefix used for resources that need unique names."
+ type = string
+}
+
+variable "project_config" {
+ description = "Provide 'billing_account_id' value if project creation is needed, uses existing 'project_ids' if null. Parent is in 'folders/nnn' or 'organizations/nnn' format."
+ type = object({
+ billing_account_id = optional(string, null)
+ project_ids = optional(object({
+ sec-core = string
+ audit-logs = string
+ }), {
+ sec-core = "sec-core"
+ audit-logs = "audit-logs"
+ }
+ )
+ })
+ nullable = false
+ validation {
+ condition = var.project_config.billing_account_id != null || var.project_config.project_ids != null
+ error_message = "At least one attribute should be set."
+ }
+}
+
+variable "vpc_sc_access_levels" {
+ description = "VPC SC access level definitions."
+ type = map(object({
+ combining_function = optional(string)
+ conditions = optional(list(object({
+ device_policy = optional(object({
+ allowed_device_management_levels = optional(list(string))
+ allowed_encryption_statuses = optional(list(string))
+ require_admin_approval = bool
+ require_corp_owned = bool
+ require_screen_lock = optional(bool)
+ os_constraints = optional(list(object({
+ os_type = string
+ minimum_version = optional(string)
+ require_verified_chrome_os = optional(bool)
+ })))
+ }))
+ ip_subnetworks = optional(list(string), [])
+ members = optional(list(string), [])
+ negate = optional(bool)
+ regions = optional(list(string), [])
+ required_access_levels = optional(list(string), [])
+ })), [])
+ description = optional(string)
+ }))
+ default = {}
+ nullable = false
+}
+
+variable "vpc_sc_egress_policies" {
+ description = "VPC SC egress policy defnitions."
+ type = map(object({
+ from = object({
+ identity_type = optional(string, "ANY_IDENTITY")
+ identities = optional(list(string))
+ })
+ to = object({
+ operations = optional(list(object({
+ method_selectors = optional(list(string))
+ service_name = string
+ })), [])
+ resources = optional(list(string))
+ resource_type_external = optional(bool, false)
+ })
+ }))
+ default = {}
+ nullable = false
+}
+
+variable "vpc_sc_ingress_policies" {
+ description = "VPC SC ingress policy defnitions."
+ type = map(object({
+ from = object({
+ access_levels = optional(list(string), [])
+ identity_type = optional(string)
+ identities = optional(list(string))
+ resources = optional(list(string), [])
+ })
+ to = object({
+ operations = optional(list(object({
+ method_selectors = optional(list(string))
+ service_name = string
+ })), [])
+ resources = optional(list(string))
+ })
+ }))
+ default = {}
+ nullable = false
+}
diff --git a/blueprints/data-solutions/sqlserver-alwayson/vpc.tf b/blueprints/data-solutions/sqlserver-alwayson/vpc.tf
index ccc10e1c3..0f1e425e1 100644
--- a/blueprints/data-solutions/sqlserver-alwayson/vpc.tf
+++ b/blueprints/data-solutions/sqlserver-alwayson/vpc.tf
@@ -85,6 +85,7 @@ module "firewall" {
ingress_rules = {
"${var.prefix}-allow-all-between-wsfc-nodes" = {
description = "Allow all between WSFC nodes"
+ source_ranges = []
sources = [module.compute-service-account.email]
targets = [module.compute-service-account.email]
use_service_accounts = true
@@ -96,6 +97,7 @@ module "firewall" {
}
"${var.prefix}-allow-all-between-wsfc-witness" = {
description = "Allow all between WSFC witness nodes"
+ source_ranges = []
sources = [module.compute-service-account.email]
targets = [module.witness-service-account.email]
use_service_accounts = true
@@ -108,7 +110,7 @@ module "firewall" {
"${var.prefix}-allow-sql-to-wsfc-nodes" = {
description = "Allow SQL connections to WSFC nodes"
targets = [module.compute-service-account.email]
- ranges = var.sql_client_cidrs
+ source_ranges = var.sql_client_cidrs
use_service_accounts = true
rules = [
{ protocol = "tcp", ports = [1433] },
@@ -117,7 +119,7 @@ module "firewall" {
"${var.prefix}-allow-health-check-to-wsfc-nodes" = {
description = "Allow health checks to WSFC nodes"
targets = [module.compute-service-account.email]
- ranges = var.health_check_ranges
+ source_ranges = var.health_check_ranges
use_service_accounts = true
rules = [
{ protocol = "tcp" }
diff --git a/blueprints/data-solutions/vertex-mlops/README.md b/blueprints/data-solutions/vertex-mlops/README.md
new file mode 100644
index 000000000..d9f85fd83
--- /dev/null
+++ b/blueprints/data-solutions/vertex-mlops/README.md
@@ -0,0 +1,79 @@
+# MLOps with Vertex AI
+
+## Introduction
+This example implements the infrastructure required to deploy an end-to-end [MLOps process](https://services.google.com/fh/files/misc/practitioners_guide_to_mlops_whitepaper.pdf) using [Vertex AI](https://cloud.google.com/vertex-ai) platform.
+
+## GCP resources
+The blueprint will deploy all the required resources to have a fully functional MLOPs environment containing:
+- Vertex Workbench (for the experimentation environment)
+- GCP Project (optional) to host all the resources
+- Isolated VPC network and a subnet to be used by Vertex and Dataflow. Alternatively, an external Shared VPC can be configured using the `network_config`variable.
+- Firewall rule to allow the internal subnet communication required by Dataflow
+- Cloud NAT required to reach the internet from the different computing resources (Vertex and Dataflow)
+- GCS buckets to host Vertex AI and Cloud Build Artifacts. By default the buckets will be regional and should match the Vertex AI region for the different resources (i.e. Vertex Managed Dataset) and processes (i.e. Vertex trainining)
+- BigQuery Dataset where the training data will be stored. This is optional, since the training data could be already hosted in an existing BigQuery dataset.
+- Artifact Registry Docker repository to host the custom images.
+- Service account (`mlops-[env]@`) with the minimum permissions required by Vertex AI and Dataflow (if this service is used inside of the Vertex AI Pipeline).
+- Service account (`github@`) to be used by Workload Identity Federation, to federate Github identity (Optional).
+- Secret to store the Github SSH key to get access the CICD code repo.
+
+
+
+## Pre-requirements
+
+### User groups
+
+Assign roles relying on User groups is a way to decouple the final set of permissions from the stage where entities and resources are created, and their IAM bindings defined. You can configure the group names through the `groups` variable. These groups should be created before launching Terraform.
+
+We use the following groups to control access to resources:
+
+- *Data Scientits* (gcp-ml-ds@string | ✓ | |
+| [bucket_name](variables.tf#L18) | GCS bucket name to store the Vertex AI artifacts. | string | | null |
+| [dataset_name](variables.tf#L24) | BigQuery Dataset to store the training data. | string | | null |
+| [groups](variables.tf#L30) | Name of the groups (name@domain.org) to apply opinionated IAM permissions. | object({…}) | | {…} |
+| [identity_pool_claims](variables.tf#L45) | Claims to be used by Workload Identity Federation (i.e.: attribute.repository/ORGANIZATION/REPO). If a not null value is provided, then google_iam_workload_identity_pool resource will be created. | string | | null |
+| [labels](variables.tf#L51) | Labels to be assigned at project level. | map(string) | | {} |
+| [location](variables.tf#L57) | Location used for multi-regional resources. | string | | "eu" |
+| [network_config](variables.tf#L63) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | object({…}) | | null |
+| [notebooks](variables.tf#L73) | Vertex AI workbenchs to be deployed. | map(object({…})) | | {} |
+| [prefix](variables.tf#L86) | Prefix used for the project id. | string | | null |
+| [project_create](variables.tf#L92) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null |
+| [project_services](variables.tf#L106) | List of core services enabled on all projects. | list(string) | | […] |
+| [region](variables.tf#L126) | Region used for regional resources. | string | | "europe-west4" |
+| [repo_name](variables.tf#L132) | Cloud Source Repository name. null to avoid to create it. | string | | null |
+| [sa_mlops_name](variables.tf#L138) | Name for the MLOPs Service Account. | string | | "sa-mlops" |
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| [github](outputs.tf#L33) | Github Configuration. | |
+| [notebook](outputs.tf#L39) | Vertex AI managed notebook details. | |
+| [project](outputs.tf#L44) | The project resource as return by the `project` module. | |
+| [project_id](outputs.tf#L49) | Project ID. | |
+
+
+# TODO
+- Add support for User Managed Notebooks, SA permission option and non default SA for Single User mode.
+- Improve default naming for local VPC and Cloud NAT
\ No newline at end of file
diff --git a/blueprints/data-solutions/vertex-mlops/ci-cd.tf b/blueprints/data-solutions/vertex-mlops/ci-cd.tf
new file mode 100644
index 000000000..d73eacc83
--- /dev/null
+++ b/blueprints/data-solutions/vertex-mlops/ci-cd.tf
@@ -0,0 +1,74 @@
+/**
+ * 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.
+ */
+
+resource "google_iam_workload_identity_pool" "github_pool" {
+ count = var.identity_pool_claims == null ? 0 : 1
+ project = module.project.project_id
+ workload_identity_pool_id = "gh-pool"
+ display_name = "Github Actions Identity Pool"
+ description = "Identity pool for Github Actions"
+}
+
+resource "google_iam_workload_identity_pool_provider" "github_provider" {
+ count = var.identity_pool_claims == null ? 0 : 1
+ project = module.project.project_id
+ workload_identity_pool_id = google_iam_workload_identity_pool.github_pool[0].workload_identity_pool_id
+ workload_identity_pool_provider_id = "gh-provider"
+ display_name = "Github Actions provider"
+ description = "OIDC provider for Github Actions"
+ attribute_mapping = {
+ "google.subject" = "assertion.sub"
+ "attribute.repository" = "assertion.repository"
+ }
+ oidc {
+ issuer_uri = "https://token.actions.githubusercontent.com"
+ }
+}
+
+module "artifact_registry" {
+ source = "../../../modules/artifact-registry"
+ id = "docker-repo"
+ project_id = module.project.project_id
+ location = var.region
+ format = "DOCKER"
+ # iam = {
+ # "roles/artifactregistry.admin" = ["group:cicd@example.com"]
+ # }
+}
+
+module "service-account-github" {
+ source = "../../../modules/iam-service-account"
+ name = "sa-github"
+ project_id = module.project.project_id
+ iam = var.identity_pool_claims == null ? {} : { "roles/iam.workloadIdentityUser" = ["principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_pool[0].name}/${var.identity_pool_claims}"] }
+}
+
+# NOTE: Secret manager module at the moment does not support CMEK
+module "secret-manager" {
+ project_id = module.project.project_id
+ source = "../../../modules/secret-manager"
+ secrets = {
+ github-key = [var.region]
+ }
+ iam = {
+ github-key = {
+ "roles/secretmanager.secretAccessor" = [
+ "serviceAccount:${module.project.service_accounts.robots.cloudbuild}",
+ module.service-account-mlops.iam_email
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/blueprints/data-solutions/vertex-mlops/images/mlops_projects.png b/blueprints/data-solutions/vertex-mlops/images/mlops_projects.png
new file mode 100644
index 000000000..24017bc9d
Binary files /dev/null and b/blueprints/data-solutions/vertex-mlops/images/mlops_projects.png differ
diff --git a/blueprints/data-solutions/vertex-mlops/main.tf b/blueprints/data-solutions/vertex-mlops/main.tf
new file mode 100644
index 000000000..5f7fbc0c9
--- /dev/null
+++ b/blueprints/data-solutions/vertex-mlops/main.tf
@@ -0,0 +1,278 @@
+/**
+ * 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 {
+ group_iam = merge(
+ var.groups.gcp-ml-viewer == null ? {} : {
+ (var.groups.gcp-ml-viewer) = [
+ "roles/aiplatform.viewer",
+ "roles/artifactregistry.reader",
+ "roles/dataflow.viewer",
+ "roles/logging.viewer",
+ "roles/storage.objectViewer"
+ ]
+ },
+ var.groups.gcp-ml-ds == null ? {} : {
+ (var.groups.gcp-ml-ds) = [
+ "roles/aiplatform.admin",
+ "roles/artifactregistry.admin",
+ "roles/bigquery.dataEditor",
+ "roles/bigquery.jobUser",
+ "roles/bigquery.user",
+ "roles/cloudbuild.builds.editor",
+ "roles/cloudfunctions.developer",
+ "roles/dataflow.developer",
+ "roles/dataflow.worker",
+ "roles/iam.serviceAccountUser",
+ "roles/logging.logWriter",
+ "roles/logging.viewer",
+ "roles/notebooks.admin",
+ "roles/pubsub.editor",
+ "roles/serviceusage.serviceUsageConsumer",
+ "roles/storage.admin"
+ ]
+ },
+ var.groups.gcp-ml-eng == null ? {} : {
+ (var.groups.gcp-ml-eng) = [
+ "roles/aiplatform.admin",
+ "roles/artifactregistry.admin",
+ "roles/bigquery.dataEditor",
+ "roles/bigquery.jobUser",
+ "roles/bigquery.user",
+ "roles/dataflow.developer",
+ "roles/dataflow.worker",
+ "roles/iam.serviceAccountUser",
+ "roles/logging.logWriter",
+ "roles/logging.viewer",
+ "roles/serviceusage.serviceUsageConsumer",
+ "roles/storage.admin"
+ ]
+ }
+ )
+
+ service_encryption_keys = var.service_encryption_keys
+ shared_vpc_project = try(var.network_config.host_project, null)
+
+ subnet = (
+ local.use_shared_vpc
+ ? var.network_config.subnet_self_link
+ : values(module.vpc-local.0.subnet_self_links)[0]
+ )
+ vpc = (
+ local.use_shared_vpc
+ ? var.network_config.network_self_link
+ : module.vpc-local.0.self_link
+ )
+ use_shared_vpc = var.network_config != null
+
+ shared_vpc_bindings = {
+ "roles/compute.networkUser" = [
+ "robot-df", "notebooks"
+ ]
+ }
+
+ shared_vpc_role_members = {
+ robot-df = "serviceAccount:${module.project.service_accounts.robots.dataflow}"
+ notebooks = "serviceAccount:${module.project.service_accounts.robots.notebooks}"
+ }
+
+ # reassemble in a format suitable for for_each
+ shared_vpc_bindings_map = {
+ for binding in flatten([
+ for role, members in local.shared_vpc_bindings : [
+ for member in members : { role = role, member = member }
+ ]
+ ]) : "${binding.role}-${binding.member}" => binding
+ }
+}
+
+module "gcs-bucket" {
+ count = var.bucket_name == null ? 0 : 1
+ source = "../../../modules/gcs"
+ project_id = module.project.project_id
+ name = var.bucket_name
+ prefix = var.prefix
+ location = var.region
+ storage_class = "REGIONAL"
+ versioning = false
+ encryption_key = try(local.service_encryption_keys.storage, null)
+}
+
+# Default bucket for Cloud Build to prevent error: "'us' violates constraint ‘constraints/gcp.resourceLocations’"
+# https://stackoverflow.com/questions/53206667/cloud-build-fails-with-resource-location-constraint
+module "gcs-bucket-cloudbuild" {
+ source = "../../../modules/gcs"
+ project_id = module.project.project_id
+ name = "${var.project_id}_cloudbuild"
+ prefix = var.prefix
+ location = var.region
+ storage_class = "REGIONAL"
+ versioning = false
+ encryption_key = try(local.service_encryption_keys.storage, null)
+}
+
+module "bq-dataset" {
+ count = var.dataset_name == null ? 0 : 1
+ source = "../../../modules/bigquery-dataset"
+ project_id = module.project.project_id
+ id = var.dataset_name
+ location = var.region
+ encryption_key = try(local.service_encryption_keys.bq, null)
+}
+
+module "vpc-local" {
+ count = local.use_shared_vpc ? 0 : 1
+ source = "../../../modules/net-vpc"
+ project_id = module.project.project_id
+ name = "default"
+ subnets = [
+ {
+ "name" : "default",
+ "region" : "${var.region}",
+ "ip_cidr_range" : "10.4.0.0/24",
+ "secondary_ip_range" : null
+ }
+ ]
+ psa_config = {
+ ranges = {
+ "vertex" : "10.13.0.0/18"
+ }
+ routes = null
+ }
+}
+
+module "firewall" {
+ count = local.use_shared_vpc ? 0 : 1
+ source = "../../../modules/net-vpc-firewall"
+ project_id = module.project.project_id
+ network = module.vpc-local[0].name
+ default_rules_config = {
+ disabled = true
+ }
+ ingress_rules = {
+ dataflow-ingress = {
+ description = "Dataflow service."
+ direction = "INGRESS"
+ action = "allow"
+ sources = ["dataflow"]
+ targets = ["dataflow"]
+ ranges = []
+ use_service_accounts = false
+ rules = [{ protocol = "tcp", ports = ["12345-12346"] }]
+ extra_attributes = {}
+ }
+ }
+
+}
+
+module "cloudnat" {
+ count = local.use_shared_vpc ? 0 : 1
+ source = "../../../modules/net-cloudnat"
+ project_id = module.project.project_id
+ region = var.region
+ name = "default"
+ router_network = module.vpc-local[0].self_link
+}
+
+module "project" {
+ source = "../../../modules/project"
+ name = var.project_id
+ parent = try(var.project_create.parent, null)
+ billing_account = try(var.project_create.billing_account_id, null)
+ project_create = var.project_create != null
+ prefix = var.prefix
+ group_iam = local.group_iam
+ iam = {
+ "roles/aiplatform.user" = [module.service-account-mlops.iam_email]
+ "roles/artifactregistry.reader" = [module.service-account-mlops.iam_email]
+ "roles/artifactregistry.writer" = [module.service-account-github.iam_email]
+ "roles/bigquery.dataEditor" = [module.service-account-mlops.iam_email]
+ "roles/bigquery.jobUser" = [module.service-account-mlops.iam_email]
+ "roles/bigquery.user" = [module.service-account-mlops.iam_email]
+ "roles/cloudbuild.builds.editor" = [
+ module.service-account-mlops.iam_email,
+ module.service-account-github.iam_email
+ ]
+
+ "roles/cloudfunctions.invoker" = [module.service-account-mlops.iam_email]
+ "roles/dataflow.developer" = [module.service-account-mlops.iam_email]
+ "roles/dataflow.worker" = [module.service-account-mlops.iam_email]
+ "roles/iam.serviceAccountUser" = [
+ module.service-account-mlops.iam_email,
+ "serviceAccount:${module.project.service_accounts.robots.cloudbuild}"
+ ]
+ "roles/monitoring.metricWriter" = [module.service-account-mlops.iam_email]
+ "roles/run.invoker" = [module.service-account-mlops.iam_email]
+ "roles/serviceusage.serviceUsageConsumer" = [
+ module.service-account-mlops.iam_email,
+ module.service-account-github.iam_email
+ ]
+ "roles/storage.admin" = [
+ module.service-account-mlops.iam_email,
+ module.service-account-github.iam_email
+ ]
+ }
+ labels = var.labels
+
+ org_policies = {
+ # Example of applying a project wide policy
+ # "constraints/compute.requireOsLogin" = {
+ # enforce = false
+ # }
+ }
+
+ service_encryption_key_ids = {
+ bq = [try(local.service_encryption_keys.bq, null)]
+ compute = [try(local.service_encryption_keys.compute, null)]
+ cloudbuild = [try(local.service_encryption_keys.storage, null)]
+ notebooks = [try(local.service_encryption_keys.compute, null)]
+ storage = [try(local.service_encryption_keys.storage, null)]
+ }
+ services = var.project_services
+
+
+ shared_vpc_service_config = local.shared_vpc_project == null ? null : {
+ attach = true
+ host_project = local.shared_vpc_project
+ }
+
+}
+
+module "service-account-mlops" {
+ source = "../../../modules/iam-service-account"
+ name = var.sa_mlops_name
+ project_id = module.project.project_id
+ iam = {
+ "roles/iam.serviceAccountUser" = [module.service-account-github.iam_email]
+ }
+}
+
+resource "google_project_iam_member" "shared_vpc" {
+ count = local.use_shared_vpc ? 1 : 0
+ project = var.network_config.host_project
+ role = "roles/compute.networkUser"
+ member = "serviceAccount:${module.project.service_accounts.robots.notebooks}"
+}
+
+
+resource "google_sourcerepo_repository" "code-repo" {
+ count = var.repo_name == null ? 0 : 1
+ name = var.repo_name
+ project = module.project.project_id
+}
+
+
diff --git a/blueprints/data-solutions/vertex-mlops/notebooks.tf b/blueprints/data-solutions/vertex-mlops/notebooks.tf
new file mode 100644
index 000000000..09d3e5a8b
--- /dev/null
+++ b/blueprints/data-solutions/vertex-mlops/notebooks.tf
@@ -0,0 +1,60 @@
+/**
+ * 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.
+ */
+
+resource "google_notebooks_runtime" "runtime" {
+ for_each = var.notebooks
+ name = each.key
+
+ project = module.project.project_id
+ location = var.notebooks[each.key].region
+ access_config {
+ access_type = "SINGLE_USER"
+ runtime_owner = var.notebooks[each.key].owner
+ }
+ software_config {
+ enable_health_monitoring = true
+ idle_shutdown = var.notebooks[each.key].idle_shutdown
+ idle_shutdown_timeout = 1800
+ }
+ virtual_machine {
+ virtual_machine_config {
+ machine_type = "n1-standard-4"
+ network = local.vpc
+ subnet = local.subnet
+ internal_ip_only = var.notebooks[each.key].internal_ip_only
+ dynamic "encryption_config" {
+ for_each = try(local.service_encryption_keys.compute, null) == null ? [] : [1]
+ content {
+ kms_key = local.service_encryption_keys.compute
+ }
+ }
+ metadata = {
+ notebook-disable-nbconvert = "false"
+ notebook-disable-downloads = "false"
+ notebook-disable-terminal = "false"
+ #notebook-disable-root = "true"
+ #notebook-upgrade-schedule = "48 4 * * MON"
+ }
+ data_disk {
+ initialize_params {
+ disk_size_gb = "100"
+ disk_type = "PD_STANDARD"
+ }
+ }
+ }
+ }
+}
+
diff --git a/blueprints/data-solutions/vertex-mlops/outputs.tf b/blueprints/data-solutions/vertex-mlops/outputs.tf
new file mode 100644
index 000000000..9cb390d62
--- /dev/null
+++ b/blueprints/data-solutions/vertex-mlops/outputs.tf
@@ -0,0 +1,52 @@
+/**
+ * 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.
+ */
+
+# TODO(): proper outputs
+
+
+locals {
+ docker_split = try(split("/", module.artifact_registry.id), null)
+ docker_repo = try("${local.docker_split[3]}-docker.pkg.dev/${local.docker_split[1]}/${local.docker_split[5]}", null)
+ gh_config = {
+ WORKLOAD_ID_PROVIDER = try(google_iam_workload_identity_pool_provider.github_provider[0].name, null)
+ SERVICE_ACCOUNT = try(module.service-account-github.email, null)
+ PROJECT_ID = module.project.project_id
+ DOCKER_REPO = local.docker_repo
+ SA_MLOPS = module.service-account-mlops.email
+ SUBNETWORK = local.subnet
+ }
+}
+
+output "github" {
+
+ description = "Github Configuration."
+ value = local.gh_config
+}
+
+output "notebook" {
+ description = "Vertex AI managed notebook details."
+ value = { for k, v in resource.google_notebooks_runtime.runtime : k => v.id }
+}
+
+output "project" {
+ description = "The project resource as return by the `project` module."
+ value = module.project
+}
+
+output "project_id" {
+ description = "Project ID."
+ value = module.project.project_id
+}
diff --git a/blueprints/data-solutions/vertex-mlops/terraform.tfvars.sample b/blueprints/data-solutions/vertex-mlops/terraform.tfvars.sample
new file mode 100644
index 000000000..097bac3a8
--- /dev/null
+++ b/blueprints/data-solutions/vertex-mlops/terraform.tfvars.sample
@@ -0,0 +1,20 @@
+bucket_name = "creditcards-dev"
+dataset_name = "creditcards"
+identity_pool_claims = "attribute.repository/ORGANIZATION/REPO"
+labels = {
+ "env" : "dev",
+ "team" : "ml"
+}
+notebooks = {
+ "myworkbench" : {
+ "owner" : "user@example.com",
+ "region" : "europe-west4",
+ "subnet" : "default",
+ }
+}
+prefix = "pref"
+project_id = "creditcards-dev"
+project_create = {
+ billing_account_id = "000000-123456-123456"
+ parent = "folders/111111111111"
+}
diff --git a/blueprints/data-solutions/vertex-mlops/variables.tf b/blueprints/data-solutions/vertex-mlops/variables.tf
new file mode 100644
index 000000000..f3f6efad3
--- /dev/null
+++ b/blueprints/data-solutions/vertex-mlops/variables.tf
@@ -0,0 +1,152 @@
+/**
+ * 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 "bucket_name" {
+ description = "GCS bucket name to store the Vertex AI artifacts."
+ type = string
+ default = null
+}
+
+variable "dataset_name" {
+ description = "BigQuery Dataset to store the training data."
+ type = string
+ default = null
+}
+
+variable "groups" {
+ description = "Name of the groups (name@domain.org) to apply opinionated IAM permissions."
+ type = object({
+ gcp-ml-ds = string
+ gcp-ml-eng = string
+ gcp-ml-viewer = string
+ })
+ default = {
+ gcp-ml-ds = null
+ gcp-ml-eng = null
+ gcp-ml-viewer = null
+ }
+ nullable = false
+}
+
+variable "identity_pool_claims" {
+ description = "Claims to be used by Workload Identity Federation (i.e.: attribute.repository/ORGANIZATION/REPO). If a not null value is provided, then google_iam_workload_identity_pool resource will be created."
+ type = string
+ default = null
+}
+
+variable "labels" {
+ description = "Labels to be assigned at project level."
+ type = map(string)
+ default = {}
+}
+
+variable "location" {
+ description = "Location used for multi-regional resources."
+ type = string
+ default = "eu"
+}
+
+variable "network_config" {
+ description = "Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values."
+ type = object({
+ host_project = string
+ network_self_link = string
+ subnet_self_link = string
+ })
+ default = null
+}
+
+variable "notebooks" {
+ description = "Vertex AI workbenchs to be deployed."
+ type = map(object({
+ owner = string
+ region = string
+ subnet = string
+ internal_ip_only = optional(bool, false)
+ idle_shutdown = optional(bool)
+ }))
+ default = {}
+ nullable = false
+}
+
+variable "prefix" {
+ description = "Prefix used for the project id."
+ type = string
+ default = null
+}
+
+variable "project_create" {
+ description = "Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format."
+ type = object({
+ billing_account_id = string
+ parent = string
+ })
+ default = null
+}
+
+variable "project_id" {
+ description = "Project id, references existing project if `project_create` is null."
+ type = string
+}
+
+variable "project_services" {
+ description = "List of core services enabled on all projects."
+ type = list(string)
+ default = [
+ "aiplatform.googleapis.com",
+ "artifactregistry.googleapis.com",
+ "bigquery.googleapis.com",
+ "cloudbuild.googleapis.com",
+ "compute.googleapis.com",
+ "datacatalog.googleapis.com",
+ "dataflow.googleapis.com",
+ "iam.googleapis.com",
+ "monitoring.googleapis.com",
+ "notebooks.googleapis.com",
+ "secretmanager.googleapis.com",
+ "servicenetworking.googleapis.com",
+ "serviceusage.googleapis.com"
+ ]
+}
+
+variable "region" {
+ description = "Region used for regional resources."
+ type = string
+ default = "europe-west4"
+}
+
+variable "repo_name" {
+ description = "Cloud Source Repository name. null to avoid to create it."
+ type = string
+ default = null
+}
+
+variable "sa_mlops_name" {
+ description = "Name for the MLOPs Service Account."
+ type = string
+ default = "sa-mlops"
+}
+
+variable "service_encryption_keys" { # service encription key
+ description = "Cloud KMS to use to encrypt different services. Key location should match service region."
+ type = object({
+ bq = string
+ compute = string
+ storage = string
+ })
+ default = null
+}
\ No newline at end of file
diff --git a/blueprints/factories/bigquery-factory/README.md b/blueprints/factories/bigquery-factory/README.md
index 05cabffb2..2cba6e01f 100644
--- a/blueprints/factories/bigquery-factory/README.md
+++ b/blueprints/factories/bigquery-factory/README.md
@@ -1,36 +1,18 @@
# Google Cloud BQ Factory
-This module allows creation and management of BigQuery datasets and views as well as tables by defining them in well formatted `yaml` files.
+This module allows creation and management of BigQuery datasets tables and views by defining them in well-formatted YAML files. YAML abstraction for BQ can simplify users onboarding and also makes creation of tables easier compared to HCL.
-Yaml abstraction for BQ can simplify users onboarding and also makes creation of tables easier compared to HCL.
+This factory is based on the [BQ dataset module](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/modules/bigquery-dataset) which currently only supports tables and views. As soon as external table and materialized view support is added, this factory will be enhanced accordingly.
-Subfolders distinguish between views and tables and ensures easier navigation for users.
-
-This factory is based on the [BQ dataset module](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/modules/bigquery-dataset) which currently only supports tables and views. As soon as external table and materialized view support is added, factory will be enhanced accordingly.
-
-You can create as many files as you like, the code will loop through it and create the required variables in order to execute everything accordingly.
+You can create as many files as you like, the code will loop through it and create everything accordingly.
## Example
### Terraform code
-```hcl
-module "bq" {
- source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric/modules/bigquery-dataset"
-
- for_each = local.output
- project_id = var.project_id
- id = each.key
- views = try(each.value.views, null)
- tables = try(each.value.tables, null)
-}
-# tftest skip
-```
-
-### Configuration Structure
-
+In this section we show how to create tables and views from a file structure simlar to the one shown below.
```bash
-base_folder
+bigquery
│
├── tables
│ ├── table_a.yaml
@@ -40,32 +22,43 @@ base_folder
│ ├── view_b.yaml
```
-## YAML structure and definition formatting
-
-### Tables
-
-Table definition to be placed in a set of yaml files in the corresponding subfolder. Structure should look as following:
+First we create the table definition in `bigquery/tables/countries.yaml`.
```yaml
-
-dataset: # required name of the dataset the table is to be placed in
-table: # required descriptive name of the table
-schema: # required schema in JSON FORMAT Example: [{name: "test", type: "STRING"},{name: "test2", type: "INT64"}]
-labels: # not required, defaults to {}, Example: {"a":"thisislabela","b":"thisislabelb"}
-use_legacy_sql: boolean # not required, defaults to false
-deletion_protection: boolean # not required, defaults to false
+# tftest-file id=table path=bigquery/tables/countries.yaml
+dataset: my_dataset
+table: countries
+deletion_protection: true
+labels:
+ env: prod
+schema:
+ - name: country
+ type: STRING
+ - name: population
+ type: INT64
```
-### Views
-View definition to be placed in a set of yaml files in the corresponding subfolder. Structure should look as following:
+And a view in `bigquery/views/population.yaml`.
```yaml
-dataset: # required, name of the dataset the view is to be placed in
-view: # required, descriptive name of the view
-query: # required, SQL Query for the view in quotes
-labels: # not required, defaults to {}, Example: {"a":"thisislabela","b":"thisislabelb"}
-use_legacy_sql: bool # not required, defaults to false
-deletion_protection: bool # not required, defaults to false
+# tftest-file id=view path=bigquery/views/population.yaml
+dataset: my_dataset
+view: department
+query: SELECT SUM(population) from my_dataset.countries
+labels:
+ env: prod
+```
+
+With this file structure, we can use the factory as follows:
+
+```hcl
+module "bq" {
+ source = "./fabric/blueprints/factories/bigquery-factory"
+ project_id = var.project_id
+ tables_path = "bigquery/tables"
+ views_path = "bigquery/views"
+}
+# tftest modules=2 resources=3 files=table,view inventory=simple.yaml
```
@@ -74,8 +67,8 @@ deletion_protection: bool # not required, defaults to false
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [project_id](variables.tf#L17) | Project ID. | string | ✓ | |
-| [tables_dir](variables.tf#L22) | Relative path for the folder storing table data. | string | ✓ | |
-| [views_dir](variables.tf#L27) | Relative path for the folder storing view data. | string | ✓ | |
+| [tables_path](variables.tf#L22) | Relative path for the folder storing table data. | string | ✓ | |
+| [views_path](variables.tf#L27) | Relative path for the folder storing view data. | string | ✓ | |
## TODO
diff --git a/blueprints/factories/bigquery-factory/main.tf b/blueprints/factories/bigquery-factory/main.tf
index 5995ea191..8c26f7479 100644
--- a/blueprints/factories/bigquery-factory/main.tf
+++ b/blueprints/factories/bigquery-factory/main.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,17 +16,22 @@
locals {
views = {
- for f in fileset("${var.views_dir}", "**/*.yaml") :
- trimsuffix(f, ".yaml") => yamldecode(file("${var.views_dir}/${f}"))
+ for f in fileset(var.views_path, "**/*.yaml") :
+ trimsuffix(f, ".yaml") => yamldecode(file("${var.views_path}/${f}"))
}
tables = {
- for f in fileset("${var.tables_dir}", "**/*.yaml") :
- trimsuffix(f, ".yaml") => yamldecode(file("${var.tables_dir}/${f}"))
+ for f in fileset(var.tables_path, "**/*.yaml") :
+ trimsuffix(f, ".yaml") => yamldecode(file("${var.tables_path}/${f}"))
}
- output = {
- for dataset in distinct([for v in values(merge(local.views, local.tables)) : v.dataset]) :
+ all_datasets = distinct(concat(
+ [for x in values(local.tables) : x.dataset],
+ [for x in values(local.views) : x.dataset]
+ ))
+
+ datasets = {
+ for dataset in local.all_datasets :
dataset => {
"views" = {
for k, v in local.views :
@@ -57,9 +62,8 @@ locals {
}
module "bq" {
- source = "../../../modules/bigquery-dataset"
-
- for_each = local.output
+ source = "../../../modules/bigquery-dataset"
+ for_each = local.datasets
project_id = var.project_id
id = each.key
views = try(each.value.views, null)
diff --git a/blueprints/factories/bigquery-factory/variables.tf b/blueprints/factories/bigquery-factory/variables.tf
index 774ec86e1..57025f629 100644
--- a/blueprints/factories/bigquery-factory/variables.tf
+++ b/blueprints/factories/bigquery-factory/variables.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,12 +19,12 @@ variable "project_id" {
type = string
}
-variable "tables_dir" {
+variable "tables_path" {
description = "Relative path for the folder storing table data."
type = string
}
-variable "views_dir" {
+variable "views_path" {
description = "Relative path for the folder storing view data."
type = string
}
diff --git a/blueprints/factories/cloud-identity-group-factory/README.md b/blueprints/factories/cloud-identity-group-factory/README.md
index 052b9a4af..b833304eb 100644
--- a/blueprints/factories/cloud-identity-group-factory/README.md
+++ b/blueprints/factories/cloud-identity-group-factory/README.md
@@ -11,9 +11,9 @@ Yaml abstraction for Groups can simplify groups creation and members management.
```hcl
module "prod-firewall" {
source = "./fabric/blueprints/factories/cloud-identity-group-factory"
-
- customer_id = "customers/C0xxxxxxx"
- data_dir = "data"
+
+ customer_id = "customers/C0xxxxxxx"
+ data_dir = "data"
}
# tftest skip
```
diff --git a/blueprints/factories/net-vpc-firewall-yaml/README.md b/blueprints/factories/net-vpc-firewall-yaml/README.md
index 26e85c5d8..5e7260e94 100644
--- a/blueprints/factories/net-vpc-firewall-yaml/README.md
+++ b/blueprints/factories/net-vpc-firewall-yaml/README.md
@@ -14,14 +14,14 @@ Nested folder structure for yaml configurations is optionally supported, which a
module "prod-firewall" {
source = "./fabric/blueprints/factories/net-vpc-firewall-yaml"
- project_id = "my-prod-project"
- network = "my-prod-network"
+ project_id = "my-prod-project"
+ network = "my-prod-network"
config_directories = [
"./prod",
"./common"
]
- log_config = {
+ log_config = {
metadata = "INCLUDE_ALL_METADATA"
}
}
@@ -29,8 +29,8 @@ module "prod-firewall" {
module "dev-firewall" {
source = "./fabric/blueprints/factories/net-vpc-firewall-yaml"
- project_id = "my-dev-project"
- network = "my-dev-network"
+ project_id = "my-dev-project"
+ network = "my-dev-network"
config_directories = [
"./dev",
"./common"
diff --git a/blueprints/factories/net-vpc-firewall-yaml/versions.tf b/blueprints/factories/net-vpc-firewall-yaml/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/factories/net-vpc-firewall-yaml/versions.tf
+++ b/blueprints/factories/net-vpc-firewall-yaml/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/factories/project-factory/README.md b/blueprints/factories/project-factory/README.md
index a5680781a..2b8c3874e 100644
--- a/blueprints/factories/project-factory/README.md
+++ b/blueprints/factories/project-factory/README.md
@@ -49,8 +49,8 @@ locals {
trimsuffix(f, ".yaml") => yamldecode(file("${local._data_dir}/${f}"))
}
# these are usually set via variables
- _base_dir = "./fabric/blueprints/factories/project-factory"
- _data_dir = "${local._base_dir}/sample-data/projects/"
+ _base_dir = "./fabric/blueprints/factories/project-factory"
+ _data_dir = "${local._base_dir}/sample-data/projects/"
_defaults_file = "${local._base_dir}/sample-data/defaults.yaml"
}
@@ -59,6 +59,7 @@ module "projects" {
for_each = local.projects
defaults = local.defaults
project_id = each.key
+ descriptive_name = try(each.value.descriptive_name, null)
billing_account_id = try(each.value.billing_account_id, null)
billing_alert = try(each.value.billing_alert, null)
dns_zones = try(each.value.dns_zones, [])
@@ -222,28 +223,29 @@ vpc:
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [billing_account_id](variables.tf#L17) | Billing account id. | string | ✓ | |
-| [prefix](variables.tf#L151) | Prefix used for resource names. | string | ✓ | |
-| [project_id](variables.tf#L160) | Project id. | string | ✓ | |
+| [prefix](variables.tf#L157) | Prefix used for resource names. | string | ✓ | |
+| [project_id](variables.tf#L166) | Project id. | string | ✓ | |
| [billing_alert](variables.tf#L22) | Billing alert configuration. | object({…}) | | null |
| [defaults](variables.tf#L35) | Project factory default values. | object({…}) | | null |
-| [dns_zones](variables.tf#L57) | DNS private zones to create as child of var.defaults.environment_dns_zone. | list(string) | | [] |
-| [essential_contacts](variables.tf#L63) | Email contacts to be used for billing and GCP notifications. | list(string) | | [] |
-| [folder_id](variables.tf#L69) | Folder ID for the folder where the project will be created. | string | | null |
-| [group_iam](variables.tf#L75) | Custom IAM settings in group => [role] format. | map(list(string)) | | {} |
-| [group_iam_additive](variables.tf#L81) | Custom additive IAM settings in group => [role] format. | map(list(string)) | | {} |
-| [iam](variables.tf#L87) | Custom IAM settings in role => [principal] format. | map(list(string)) | | {} |
-| [iam_additive](variables.tf#L93) | Custom additive IAM settings in role => [principal] format. | map(list(string)) | | {} |
-| [kms_service_agents](variables.tf#L99) | KMS IAM configuration in as service => [key]. | map(list(string)) | | {} |
-| [labels](variables.tf#L105) | Labels to be assigned at project level. | map(string) | | {} |
-| [org_policies](variables.tf#L111) | Org-policy overrides at project level. | map(object({…})) | | {} |
-| [service_accounts](variables.tf#L165) | Service accounts to be created, and roles assigned them on the project. | map(list(string)) | | {} |
-| [service_accounts_additive](variables.tf#L171) | Service accounts to be created, and roles assigned them on the project additively. | map(list(string)) | | {} |
-| [service_accounts_iam](variables.tf#L177) | IAM bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}. | map(map(list(string))) | | {} |
-| [service_accounts_iam_additive](variables.tf#L184) | IAM additive bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}. | map(map(list(string))) | | {} |
-| [service_identities_iam](variables.tf#L191) | Custom IAM settings for service identities in service => [role] format. | map(list(string)) | | {} |
-| [service_identities_iam_additive](variables.tf#L198) | Custom additive IAM settings for service identities in service => [role] format. | map(list(string)) | | {} |
-| [services](variables.tf#L205) | Services to be enabled for the project. | list(string) | | [] |
-| [vpc](variables.tf#L212) | VPC configuration for the project. | object({…}) | | null |
+| [descriptive_name](variables.tf#L57) | Name of the project name. Used for project name instead of `name` variable. | string | | null |
+| [dns_zones](variables.tf#L63) | DNS private zones to create as child of var.defaults.environment_dns_zone. | list(string) | | [] |
+| [essential_contacts](variables.tf#L69) | Email contacts to be used for billing and GCP notifications. | list(string) | | [] |
+| [folder_id](variables.tf#L75) | Folder ID for the folder where the project will be created. | string | | null |
+| [group_iam](variables.tf#L81) | Custom IAM settings in group => [role] format. | map(list(string)) | | {} |
+| [group_iam_additive](variables.tf#L87) | Custom additive IAM settings in group => [role] format. | map(list(string)) | | {} |
+| [iam](variables.tf#L93) | Custom IAM settings in role => [principal] format. | map(list(string)) | | {} |
+| [iam_additive](variables.tf#L99) | Custom additive IAM settings in role => [principal] format. | map(list(string)) | | {} |
+| [kms_service_agents](variables.tf#L105) | KMS IAM configuration in as service => [key]. | map(list(string)) | | {} |
+| [labels](variables.tf#L111) | Labels to be assigned at project level. | map(string) | | {} |
+| [org_policies](variables.tf#L117) | Org-policy overrides at project level. | map(object({…})) | | {} |
+| [service_accounts](variables.tf#L171) | Service accounts to be created, and roles assigned them on the project. | map(list(string)) | | {} |
+| [service_accounts_additive](variables.tf#L177) | Service accounts to be created, and roles assigned them on the project additively. | map(list(string)) | | {} |
+| [service_accounts_iam](variables.tf#L183) | IAM bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}. | map(map(list(string))) | | {} |
+| [service_accounts_iam_additive](variables.tf#L190) | IAM additive bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}. | map(map(list(string))) | | {} |
+| [service_identities_iam](variables.tf#L197) | Custom IAM settings for service identities in service => [role] format. | map(list(string)) | | {} |
+| [service_identities_iam_additive](variables.tf#L204) | Custom additive IAM settings for service identities in service => [role] format. | map(list(string)) | | {} |
+| [services](variables.tf#L211) | Services to be enabled for the project. | list(string) | | [] |
+| [vpc](variables.tf#L218) | VPC configuration for the project. | object({…}) | | null |
## Outputs
diff --git a/blueprints/factories/project-factory/main.tf b/blueprints/factories/project-factory/main.tf
index f6b2a797c..518d5a69c 100644
--- a/blueprints/factories/project-factory/main.tf
+++ b/blueprints/factories/project-factory/main.tf
@@ -180,6 +180,7 @@ module "project" {
source = "../../../modules/project"
billing_account = local.billing_account_id
name = var.project_id
+ descriptive_name = var.descriptive_name
prefix = var.prefix
contacts = { for c in local.essential_contacts : c => ["ALL"] }
iam = local.iam
diff --git a/blueprints/factories/project-factory/variables.tf b/blueprints/factories/project-factory/variables.tf
index 0ece0f042..3aa3fa36b 100644
--- a/blueprints/factories/project-factory/variables.tf
+++ b/blueprints/factories/project-factory/variables.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,6 +54,12 @@ variable "defaults" {
default = null
}
+variable "descriptive_name" {
+ description = "Name of the project name. Used for project name instead of `name` variable."
+ type = string
+ default = null
+}
+
variable "dns_zones" {
description = "DNS private zones to create as child of var.defaults.environment_dns_zone."
type = list(string)
diff --git a/blueprints/gke/multi-cluster-mesh-gke-fleet-api/ansible/roles/install/tasks/install.yaml b/blueprints/gke/multi-cluster-mesh-gke-fleet-api/ansible/roles/install/tasks/install.yaml
index b81c49622..f59f03e3d 100644
--- a/blueprints/gke/multi-cluster-mesh-gke-fleet-api/ansible/roles/install/tasks/install.yaml
+++ b/blueprints/gke/multi-cluster-mesh-gke-fleet-api/ansible/roles/install/tasks/install.yaml
@@ -23,25 +23,6 @@
set_fact:
context: "gke_{{ project_id }}_{{ region }}_{{ cluster }}"
-- name: Install ASM in cluster
- shell: >
- gcloud container fleet mesh update \
- --control-plane automatic \
- --memberships {{ cluster }} \
- --project {{ project_id }}
-
-- name: Wait until MCP is provisioned
- shell: >
- for i in $(seq 12); do
- result=$(gcloud container fleet mesh describe --project {{ project_id }} --format json \
- | jq -r '.membershipStates | to_entries[] | select(.key | endswith("{{ cluster }}")) | .value.servicemesh.controlPlaneManagement.state')
- if [ "$result" = "ACTIVE" ]; then
- break
- fi
- echo "ASM control plane is not ready yet..."
- sleep 60
- done
-
- name: Get endpoint IP
shell: >
gcloud container clusters describe "{{ cluster }}" \
diff --git a/blueprints/gke/multitenant-fleet/README.md b/blueprints/gke/multitenant-fleet/README.md
index 80d09ac10..cadcf4109 100644
--- a/blueprints/gke/multitenant-fleet/README.md
+++ b/blueprints/gke/multitenant-fleet/README.md
@@ -4,7 +4,7 @@ This blueprint presents an opinionated architecture to handle multiple homogeneo
The pattern used in this design is useful, for blueprint, in cases where multiple clusters host/support the same workloads, such as in the case of a multi-regional deployment. Furthermore, combined with Anthos Config Sync and proper RBAC, this architecture can be used to host multiple tenants (e.g. teams, applications) sharing the clusters.
-This blueprint is used as part of the [FAST GKE stage](../../../fast/stages/03-gke-multitenant/) but it can also be used independently if desired.
+This blueprint is used as part of the [FAST GKE stage](../../../fast/stages/3-gke-multitenant/) but it can also be used independently if desired.
@@ -78,7 +78,7 @@ module "gke-fleet" {
location = "europe-west1"
private_cluster_config = local.cluster_defaults.private_cluster_config
vpc_config = {
- subnetwork = local.subnet_self_links.ew1
+ subnetwork = local.subnet_self_links.ew1
master_ipv4_cidr_block = "172.16.10.0/28"
}
}
@@ -86,7 +86,7 @@ module "gke-fleet" {
location = "europe-west3"
private_cluster_config = local.cluster_defaults.private_cluster_config
vpc_config = {
- subnetwork = local.subnet_self_links.ew3
+ subnetwork = local.subnet_self_links.ew3
master_ipv4_cidr_block = "172.16.20.0/28"
}
}
@@ -95,16 +95,16 @@ module "gke-fleet" {
cluster-0 = {
nodepool-0 = {
node_config = {
- disk_type = "pd-balanced"
+ disk_type = "pd-balanced"
machine_type = "n2-standard-4"
- spot = true
+ spot = true
}
}
}
cluster-1 = {
nodepool-0 = {
node_config = {
- disk_type = "pd-balanced"
+ disk_type = "pd-balanced"
machine_type = "n2-standard-4"
}
}
@@ -115,7 +115,7 @@ module "gke-fleet" {
vpc_self_link = "projects/prj-host/global/networks/prod-0"
}
}
-# tftest modules=7 resources=26
+# tftest modules=7 resources=27
```
## GKE Fleet
@@ -143,13 +143,13 @@ module "gke" {
prefix = "myprefix"
clusters = {
cluster-0 = {
- location = "europe-west1"
+ location = "europe-west1"
vpc_config = {
subnetwork = local.subnet_self_links.ew1
}
}
cluster-1 = {
- location = "europe-west3"
+ location = "europe-west3"
vpc_config = {
subnetwork = local.subnet_self_links.ew3
}
@@ -159,16 +159,16 @@ module "gke" {
cluster-0 = {
nodepool-0 = {
node_config = {
- disk_type = "pd-balanced"
+ disk_type = "pd-balanced"
machine_type = "n2-standard-4"
- spot = true
+ spot = true
}
}
}
cluster-1 = {
nodepool-0 = {
node_config = {
- disk_type = "pd-balanced"
+ disk_type = "pd-balanced"
machine_type = "n2-standard-4"
}
}
@@ -205,14 +205,14 @@ module "gke" {
enable_hierarchical_resource_quota = true
enable_pod_tree_labels = true
}
- policy_controller = {
+ policy_controller = {
audit_interval_seconds = 30
exemptable_namespaces = ["kube-system"]
log_denies_enabled = true
referential_rules_enabled = true
template_library_installed = true
}
- version = "1.10.2"
+ version = "1.10.2"
}
}
fleet_configmanagement_clusters = {
@@ -224,7 +224,7 @@ module "gke" {
}
}
-# tftest modules=8 resources=35
+# tftest modules=8 resources=38
```
diff --git a/blueprints/networking/README.md b/blueprints/networking/README.md
index ef3932689..ec510d564 100644
--- a/blueprints/networking/README.md
+++ b/blueprints/networking/README.md
@@ -49,19 +49,20 @@ The blueprint shows how to implement spoke transitivity via BGP advertisements,
### DNS and Private Access for On-premises
-
This [blueprint](./onprem-google-access-dns/) uses an emulated on-premises environment running in Docker containers inside a GCE instance, to allow testing specific features like DNS policies, DNS forwarding zones across VPN, and Private Access for On-premises hosts.
+
This [blueprint](./onprem-google-access-dns/) uses an emulated on-premises environment running in Docker containers inside a GCE instance, to allow testing specific features like DNS policies, DNS forwarding zones across VPN, and Private Access for On-premises hosts.
The emulated on-premises environment can be used to test access to different services from outside Google Cloud, by implementing a VPN connection and BGP to Google CLoud via Strongswan and Bird.
+-->
+
### Calling a private Cloud Function from on-premises
This [blueprint](./private-cloud-function-from-onprem/) shows how to invoke a [private Google Cloud Function](https://cloud.google.com/functions/docs/networking/network-settings) from the on-prem environment via a [Private Service Connect endpoint](https://cloud.google.com/vpc/docs/private-service-connect#benefits-apis).
diff --git a/blueprints/networking/_deprecated/README.md b/blueprints/networking/__need_fixing/README.md
similarity index 100%
rename from blueprints/networking/_deprecated/README.md
rename to blueprints/networking/__need_fixing/README.md
diff --git a/blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/Dockerfile b/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/Dockerfile
similarity index 100%
rename from blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/Dockerfile
rename to blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/Dockerfile
diff --git a/blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/README.md b/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/README.md
similarity index 100%
rename from blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/README.md
rename to blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/README.md
diff --git a/blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/main.tf b/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/main.tf
similarity index 100%
rename from blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/main.tf
rename to blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/main.tf
diff --git a/blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/outputs.tf b/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/outputs.tf
similarity index 100%
rename from blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/outputs.tf
rename to blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/outputs.tf
diff --git a/blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/reverse-proxy.png b/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/reverse-proxy.png
similarity index 100%
rename from blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/reverse-proxy.png
rename to blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/reverse-proxy.png
diff --git a/blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/variables.tf b/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/variables.tf
similarity index 100%
rename from blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/variables.tf
rename to blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/variables.tf
diff --git a/blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/versions.tf b/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/versions.tf
similarity index 91%
rename from blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/versions.tf
rename to blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/networking/_deprecated/nginx-reverse-proxy-cluster/versions.tf
+++ b/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/networking/onprem-google-access-dns/README.md b/blueprints/networking/__need_fixing/onprem-google-access-dns/README.md
similarity index 95%
rename from blueprints/networking/onprem-google-access-dns/README.md
rename to blueprints/networking/__need_fixing/onprem-google-access-dns/README.md
index fae563986..deb3593f7 100644
--- a/blueprints/networking/onprem-google-access-dns/README.md
+++ b/blueprints/networking/__need_fixing/onprem-google-access-dns/README.md
@@ -1,6 +1,6 @@
# On-prem DNS and Google Private Access
-This blueprint leverages the [on prem in a box](../../../modules/cloud-config-container/onprem) module to bootstrap an emulated on-premises environment on GCP, then connects it via VPN and sets up BGP and DNS so that several specific features can be tested:
+This blueprint leverages the on prem in a box module to bootstrap an emulated on-premises environment on GCP, then connects it via VPN and sets up BGP and DNS so that several specific features can be tested:
- [Cloud DNS forwarding zone](https://cloud.google.com/dns/docs/overview#fz-targets) to on-prem
- DNS forwarding from on-prem via a [Cloud DNS inbound policy](https://cloud.google.com/dns/docs/policies#create-in)
@@ -30,7 +30,7 @@ The Cloud DNS inbound policy reserves an IP address in the VPC, which is used by
### Find out the forwarder entry point address
-Run this gcloud command to (find out the address assigned to the inbound forwarder)[https://cloud.google.com/dns/docs/policies#list-in-entrypoints]:
+Run this gcloud command to [find out the address assigned to the inbound forwarder](https://cloud.google.com/dns/docs/policies#list-in-entrypoints):
```bash
gcloud compute addresses list --project [your project id]
@@ -199,7 +199,7 @@ curl www.onprem.example.org -s |grep h1
A single pre-existing project is used in this blueprint to keep variables and complexity to a minimum, in a real world scenarios each spoke would probably use a separate project.
-The VPN-s used to connect to the on-premises environment do not account for HA, upgrading to use HA VPN is reasonably simple by using the relevant [module](../../../modules/net-vpn-ha).
+The VPN-s used to connect to the on-premises environment do not account for HA, upgrading to use HA VPN is reasonably simple by using the relevant [module](../../../../modules/net-vpn-ha).
## Variables
diff --git a/blueprints/networking/onprem-google-access-dns/assets/Corefile b/blueprints/networking/__need_fixing/onprem-google-access-dns/assets/Corefile
similarity index 100%
rename from blueprints/networking/onprem-google-access-dns/assets/Corefile
rename to blueprints/networking/__need_fixing/onprem-google-access-dns/assets/Corefile
diff --git a/blueprints/networking/onprem-google-access-dns/backend.tf.sample b/blueprints/networking/__need_fixing/onprem-google-access-dns/backend.tf.sample
similarity index 100%
rename from blueprints/networking/onprem-google-access-dns/backend.tf.sample
rename to blueprints/networking/__need_fixing/onprem-google-access-dns/backend.tf.sample
diff --git a/blueprints/networking/onprem-google-access-dns/diagram.png b/blueprints/networking/__need_fixing/onprem-google-access-dns/diagram.png
similarity index 100%
rename from blueprints/networking/onprem-google-access-dns/diagram.png
rename to blueprints/networking/__need_fixing/onprem-google-access-dns/diagram.png
diff --git a/blueprints/networking/onprem-google-access-dns/main.tf b/blueprints/networking/__need_fixing/onprem-google-access-dns/main.tf
similarity index 100%
rename from blueprints/networking/onprem-google-access-dns/main.tf
rename to blueprints/networking/__need_fixing/onprem-google-access-dns/main.tf
diff --git a/blueprints/networking/onprem-google-access-dns/outputs.tf b/blueprints/networking/__need_fixing/onprem-google-access-dns/outputs.tf
similarity index 100%
rename from blueprints/networking/onprem-google-access-dns/outputs.tf
rename to blueprints/networking/__need_fixing/onprem-google-access-dns/outputs.tf
diff --git a/blueprints/networking/onprem-google-access-dns/variables.tf b/blueprints/networking/__need_fixing/onprem-google-access-dns/variables.tf
similarity index 100%
rename from blueprints/networking/onprem-google-access-dns/variables.tf
rename to blueprints/networking/__need_fixing/onprem-google-access-dns/variables.tf
diff --git a/modules/cloud-config-container/onprem/versions.tf b/blueprints/networking/__need_fixing/onprem-google-access-dns/versions.tf
similarity index 91%
rename from modules/cloud-config-container/onprem/versions.tf
rename to blueprints/networking/__need_fixing/onprem-google-access-dns/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/cloud-config-container/onprem/versions.tf
+++ b/blueprints/networking/__need_fixing/onprem-google-access-dns/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/networking/decentralized-firewall/versions.tf b/blueprints/networking/decentralized-firewall/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/networking/decentralized-firewall/versions.tf
+++ b/blueprints/networking/decentralized-firewall/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/networking/filtering-proxy-psc/versions.tf b/blueprints/networking/filtering-proxy-psc/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/networking/filtering-proxy-psc/versions.tf
+++ b/blueprints/networking/filtering-proxy-psc/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/networking/filtering-proxy/versions.tf b/blueprints/networking/filtering-proxy/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/networking/filtering-proxy/versions.tf
+++ b/blueprints/networking/filtering-proxy/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/networking/hub-and-spoke-peering/versions.tf b/blueprints/networking/hub-and-spoke-peering/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/networking/hub-and-spoke-peering/versions.tf
+++ b/blueprints/networking/hub-and-spoke-peering/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/networking/hub-and-spoke-vpn/README.md b/blueprints/networking/hub-and-spoke-vpn/README.md
index 4f580ed82..bdf877c73 100644
--- a/blueprints/networking/hub-and-spoke-vpn/README.md
+++ b/blueprints/networking/hub-and-spoke-vpn/README.md
@@ -7,7 +7,7 @@ A few additional features are also shown:
- [custom BGP advertisements](https://cloud.google.com/router/docs/how-to/advertising-overview) to implement transitivity between spokes
- [VPC Global Routing](https://cloud.google.com/network-connectivity/docs/router/how-to/configuring-routing-mode) to leverage a regional set of VPN gateways in different regions as next hops (used here for illustrative/study purpose, not usually done in real life)
-The blueprint has been purposefully kept simple to show how to use and wire the VPC and VPN-HA modules together, and so that it can be used as a basis for experimentation. For a more complex scenario that better reflects real-life usage, including [Shared VPC](https://cloud.google.com/vpc/docs/shared-vpc) and [DNS cross-project binding](https://cloud.google.com/dns/docs/zones/cross-project-binding) please refer to the [FAST network stage](../../../fast/stages/02-networking-vpn/).
+The blueprint has been purposefully kept simple to show how to use and wire the VPC and VPN-HA modules together, and so that it can be used as a basis for experimentation. For a more complex scenario that better reflects real-life usage, including [Shared VPC](https://cloud.google.com/vpc/docs/shared-vpc) and [DNS cross-project binding](https://cloud.google.com/dns/docs/zones/cross-project-binding) please refer to the [FAST network stage](../../../fast/stages/2-networking-b-vpn/).
This is the high level diagram of this blueprint:
@@ -35,12 +35,12 @@ You can easily create such a project by commenting turning on project creation i
```hcl
module "project" {
- source = "../../../modules/project"
- name = var.project_id
+ source = "../../../modules/project"
+ name = var.project_id
# comment or remove this line to enable project creation
# project_create = false
# add the following line with your billing account id value
- billing_account = "12345-ABCD-12345"
+ billing_account = "12345-ABCD-12345"
services = [
"compute.googleapis.com",
"dns.googleapis.com"
diff --git a/blueprints/networking/hub-and-spoke-vpn/versions.tf b/blueprints/networking/hub-and-spoke-vpn/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/networking/hub-and-spoke-vpn/versions.tf
+++ b/blueprints/networking/hub-and-spoke-vpn/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/networking/ilb-next-hop/versions.tf b/blueprints/networking/ilb-next-hop/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/networking/ilb-next-hop/versions.tf
+++ b/blueprints/networking/ilb-next-hop/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/networking/private-cloud-function-from-onprem/versions.tf b/blueprints/networking/private-cloud-function-from-onprem/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/networking/private-cloud-function-from-onprem/versions.tf
+++ b/blueprints/networking/private-cloud-function-from-onprem/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/networking/shared-vpc-gke/main.tf b/blueprints/networking/shared-vpc-gke/main.tf
index 2e770377f..97bf45d24 100644
--- a/blueprints/networking/shared-vpc-gke/main.tf
+++ b/blueprints/networking/shared-vpc-gke/main.tf
@@ -227,6 +227,7 @@ module "cluster-1-nodepool-1" {
project_id = module.project-svc-gke.project_id
location = module.cluster-1.0.location
cluster_name = module.cluster-1.0.name
+ cluster_id = module.cluster-1.0.id
service_account = {
create = true
}
diff --git a/blueprints/networking/shared-vpc-gke/versions.tf b/blueprints/networking/shared-vpc-gke/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/networking/shared-vpc-gke/versions.tf
+++ b/blueprints/networking/shared-vpc-gke/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/blueprints/serverless/cloud-run-explore/README.md b/blueprints/serverless/cloud-run-explore/README.md
new file mode 100644
index 000000000..1002e817e
--- /dev/null
+++ b/blueprints/serverless/cloud-run-explore/README.md
@@ -0,0 +1,214 @@
+# Cloud Run Explore
+
+## Introduction
+
+This blueprint contains all the necessary Terraform modules to build and publicly expose a Cloud Run service in a variety of use cases.
+
+The content of this blueprint corresponds to the chapter '_My serverless "Hello, World! - Exploring Cloud Run_' of the __Serverless Networking Guide__ (to be released soon). This guide is an easy to follow introduction to Cloud Run, where a couple of friendly characters will guide you from the basics to more advanced topics with a very practical approach and in record time! The code here complements this learning and allows you to test the scenarios presented and your knowledge.
+
+## Architecture
+
+The following diagram depicts the main components that this blueprint will set up:
+
+
string | ✓ | |
+| [custom_domain](variables.tf#L17) | Custom domain for the Load Balancer. | string | | null |
+| [iap](variables.tf#L23) | Identity-Aware Proxy for Cloud Run in the LB. | object({…}) | | {} |
+| [image](variables.tf#L34) | Container image to deploy. | string | | "us-docker.pkg.dev/cloudrun/container/hello" |
+| [ingress_settings](variables.tf#L40) | Ingress traffic sources allowed to call the service. | string | | "all" |
+| [project_create](variables.tf#L46) | Parameters for the creation of a new project. | object({…}) | | null |
+| [region](variables.tf#L60) | Cloud region where resource will be deployed. | string | | "europe-west1" |
+| [run_svc_name](variables.tf#L66) | Cloud Run service name. | string | | "hello" |
+| [security_policy](variables.tf#L72) | Security policy (Cloud Armor) to enforce in the LB. | object({…}) | | {} |
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| [custom_domain](outputs.tf#L19) | Custom domain for the Load Balancer. | |
+| [default_URL](outputs.tf#L24) | Cloud Run service default URL. | |
+| [load_balancer_ip](outputs.tf#L29) | LB IP that forwards to Cloud Run service. | |
+
+
+
+## Tests
+
+```hcl
+module "test" {
+ source = "./fabric/blueprints/serverless/cloud-run-explore"
+ project_create = {
+ billing_account_id = "ABCDE-12345-ABCDE"
+ parent = "organizations/0123456789"
+ }
+ project_id = "myproject"
+ custom_domain = "cloud-run-explore.example.org"
+ ingress_settings = "internal-and-cloud-load-balancing"
+ security_policy = {
+ enabled = true
+ ip_blacklist = ["79.149.0.0/16"]
+ }
+ iap = {
+ enabled = true
+ email = "user@example.org"
+ }
+}
+
+# tftest modules=4 resources=17
+```
\ No newline at end of file
diff --git a/blueprints/serverless/cloud-run-explore/images/architecture.png b/blueprints/serverless/cloud-run-explore/images/architecture.png
new file mode 100644
index 000000000..46e3a41df
Binary files /dev/null and b/blueprints/serverless/cloud-run-explore/images/architecture.png differ
diff --git a/blueprints/serverless/cloud-run-explore/images/forbidden.png b/blueprints/serverless/cloud-run-explore/images/forbidden.png
new file mode 100644
index 000000000..67d313b85
Binary files /dev/null and b/blueprints/serverless/cloud-run-explore/images/forbidden.png differ
diff --git a/blueprints/serverless/cloud-run-explore/images/service-running.png b/blueprints/serverless/cloud-run-explore/images/service-running.png
new file mode 100644
index 000000000..2c517a0ac
Binary files /dev/null and b/blueprints/serverless/cloud-run-explore/images/service-running.png differ
diff --git a/blueprints/serverless/cloud-run-explore/images/use-case-1.png b/blueprints/serverless/cloud-run-explore/images/use-case-1.png
new file mode 100644
index 000000000..8028c65da
Binary files /dev/null and b/blueprints/serverless/cloud-run-explore/images/use-case-1.png differ
diff --git a/blueprints/serverless/cloud-run-explore/images/use-case-2.png b/blueprints/serverless/cloud-run-explore/images/use-case-2.png
new file mode 100644
index 000000000..a0fafb994
Binary files /dev/null and b/blueprints/serverless/cloud-run-explore/images/use-case-2.png differ
diff --git a/blueprints/serverless/cloud-run-explore/images/use-case-3.png b/blueprints/serverless/cloud-run-explore/images/use-case-3.png
new file mode 100644
index 000000000..af834a3d1
Binary files /dev/null and b/blueprints/serverless/cloud-run-explore/images/use-case-3.png differ
diff --git a/blueprints/serverless/cloud-run-explore/images/use-case-4.png b/blueprints/serverless/cloud-run-explore/images/use-case-4.png
new file mode 100644
index 000000000..113e9206e
Binary files /dev/null and b/blueprints/serverless/cloud-run-explore/images/use-case-4.png differ
diff --git a/blueprints/serverless/cloud-run-explore/images/use-case-5.png b/blueprints/serverless/cloud-run-explore/images/use-case-5.png
new file mode 100644
index 000000000..d4f918782
Binary files /dev/null and b/blueprints/serverless/cloud-run-explore/images/use-case-5.png differ
diff --git a/blueprints/serverless/cloud-run-explore/main.tf b/blueprints/serverless/cloud-run-explore/main.tf
new file mode 100644
index 000000000..04bb3cc8b
--- /dev/null
+++ b/blueprints/serverless/cloud-run-explore/main.tf
@@ -0,0 +1,187 @@
+/**
+ * Copyright 2023 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 {
+ gclb_create = var.custom_domain == null ? false : true
+}
+
+module "project" {
+ source = "../../../modules/project"
+ billing_account = (var.project_create != null
+ ? var.project_create.billing_account_id
+ : null
+ )
+ parent = (var.project_create != null
+ ? var.project_create.parent
+ : null
+ )
+ name = var.project_id
+ services = [
+ "run.googleapis.com",
+ "compute.googleapis.com",
+ "iap.googleapis.com"
+ ]
+ project_create = var.project_create != null
+}
+
+# Cloud Run service
+module "cloud_run" {
+ source = "../../../modules/cloud-run"
+ project_id = module.project.project_id
+ name = var.run_svc_name
+ region = var.region
+ containers = [{
+ image = var.image
+ options = null
+ ports = null
+ resources = null
+ volume_mounts = null
+ }]
+ iam = {
+ "roles/run.invoker" = ["allUsers"]
+ }
+ ingress_settings = var.ingress_settings
+}
+
+# Reserved static IP for the Load Balancer
+resource "google_compute_global_address" "default" {
+ count = local.gclb_create ? 1 : 0
+ project = module.project.project_id
+ name = "glb-ip"
+}
+
+# Global L7 HTTPS Load Balancer in front of Cloud Run
+module "glb" {
+ source = "../../../modules/net-glb"
+ count = local.gclb_create ? 1 : 0
+ project_id = module.project.project_id
+ name = "glb"
+ address = google_compute_global_address.default[0].address
+ backend_service_configs = {
+ default = {
+ backends = [
+ { backend = "neg-0" }
+ ]
+ health_checks = []
+ port_name = "http"
+ security_policy = try(google_compute_security_policy.policy[0].name,
+ null)
+ iap_config = try({
+ oauth2_client_id = google_iap_client.iap_client[0].client_id,
+ oauth2_client_secret = google_iap_client.iap_client[0].secret
+ }, null)
+ }
+ }
+ health_check_configs = {}
+ neg_configs = {
+ neg-0 = {
+ cloudrun = {
+ region = var.region
+ target_service = {
+ name = var.run_svc_name
+ }
+ }
+ }
+ }
+ protocol = "HTTPS"
+ ssl_certificates = {
+ managed_configs = {
+ default = {
+ domains = [var.custom_domain]
+ }
+ }
+ }
+}
+
+# Cloud Armor configuration
+resource "google_compute_security_policy" "policy" {
+ count = local.gclb_create && var.security_policy.enabled ? 1 : 0
+ name = "cloud-run-policy"
+ project = module.project.project_id
+ rule {
+ action = "deny(403)"
+ priority = 1000
+ match {
+ versioned_expr = "SRC_IPS_V1"
+ config {
+ src_ip_ranges = var.security_policy.ip_blacklist
+ }
+ }
+ description = "Deny access to list of IPs"
+ }
+ rule {
+ action = "deny(403)"
+ priority = 900
+ match {
+ expr {
+ expression = "request.path.matches(\"${var.security_policy.path_blocked}\")"
+ }
+ }
+ description = "Deny access to specific URL paths"
+ }
+ rule {
+ action = "allow"
+ priority = "2147483647"
+ match {
+ versioned_expr = "SRC_IPS_V1"
+ config {
+ src_ip_ranges = ["*"]
+ }
+ }
+ description = "Default rule"
+ }
+}
+
+# Identity-Aware Proxy (IAP) or OAuth brand (see OAuth consent screen)
+# Note:
+# Only "Organization Internal" brands can be created programmatically
+# via API. To convert it into an external brand please use the GCP
+# Console.
+# Brands can only be created once for a Google Cloud project and the
+# underlying Google API doesn't support DELETE or PATCH methods.
+# Destroying a Terraform-managed Brand will remove it from state but
+# will not delete it from Google Cloud.
+resource "google_iap_brand" "iap_brand" {
+ count = local.gclb_create && var.iap.enabled ? 1 : 0
+ project = module.project.project_id
+ # Support email displayed on the OAuth consent screen. The caller must be
+ # the user with the associated email address, or if a group email is
+ # specified, the caller can be either a user or a service account which
+ # is an owner of the specified group in Cloud Identity.
+ support_email = var.iap.email
+ application_title = var.iap.app_title
+}
+
+# IAP owned OAuth2 client
+# Note:
+# Only internal org clients can be created via declarative tools.
+# External clients must be manually created via the GCP console.
+# Warning:
+# All arguments including secret will be stored in the raw state as plain-text.
+resource "google_iap_client" "iap_client" {
+ count = local.gclb_create && var.iap.enabled ? 1 : 0
+ display_name = var.iap.oauth2_client_name
+ brand = google_iap_brand.iap_brand[0].name
+}
+
+# IAM policy for IAP
+# For simplicity we use the same email as support_email and authorized member
+resource "google_iap_web_iam_member" "iap_iam" {
+ count = local.gclb_create && var.iap.enabled ? 1 : 0
+ project = module.project.project_id
+ role = "roles/iap.httpsResourceAccessor"
+ member = "user:${var.iap.email}"
+}
diff --git a/blueprints/serverless/cloud-run-explore/outputs.tf b/blueprints/serverless/cloud-run-explore/outputs.tf
new file mode 100644
index 000000000..2005b13d8
--- /dev/null
+++ b/blueprints/serverless/cloud-run-explore/outputs.tf
@@ -0,0 +1,32 @@
+/**
+ * Copyright 2023 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.
+ */
+
+# Custom domain for the Load Balancer. I'd prefer getting the value from the
+# SSL certificate but it is not exported as output
+output "custom_domain" {
+ description = "Custom domain for the Load Balancer."
+ value = local.gclb_create ? var.custom_domain : "none"
+}
+
+output "default_URL" {
+ description = "Cloud Run service default URL."
+ value = module.cloud_run.service.status[0].url
+}
+
+output "load_balancer_ip" {
+ description = "LB IP that forwards to Cloud Run service."
+ value = local.gclb_create ? module.glb[0].address : "none"
+}
diff --git a/blueprints/serverless/cloud-run-explore/variables.tf b/blueprints/serverless/cloud-run-explore/variables.tf
new file mode 100644
index 000000000..1c3b04a1a
--- /dev/null
+++ b/blueprints/serverless/cloud-run-explore/variables.tf
@@ -0,0 +1,80 @@
+/**
+ * Copyright 2023 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 "custom_domain" {
+ description = "Custom domain for the Load Balancer."
+ type = string
+ default = null
+}
+
+variable "iap" {
+ description = "Identity-Aware Proxy for Cloud Run in the LB."
+ type = object({
+ enabled = optional(bool, false)
+ app_title = optional(string, "Cloud Run Explore Application")
+ oauth2_client_name = optional(string, "Test Client")
+ email = optional(string)
+ })
+ default = {}
+}
+
+variable "image" {
+ description = "Container image to deploy."
+ type = string
+ default = "us-docker.pkg.dev/cloudrun/container/hello"
+}
+
+variable "ingress_settings" {
+ description = "Ingress traffic sources allowed to call the service."
+ type = string
+ default = "all"
+}
+
+variable "project_create" {
+ description = "Parameters for the creation of a new project."
+ type = object({
+ billing_account_id = string
+ parent = string
+ })
+ default = null
+}
+
+variable "project_id" {
+ description = "Project ID."
+ type = string
+}
+
+variable "region" {
+ description = "Cloud region where resource will be deployed."
+ type = string
+ default = "europe-west1"
+}
+
+variable "run_svc_name" {
+ description = "Cloud Run service name."
+ type = string
+ default = "hello"
+}
+
+variable "security_policy" {
+ description = "Security policy (Cloud Armor) to enforce in the LB."
+ type = object({
+ enabled = optional(bool, false)
+ ip_blacklist = optional(list(string), ["*"])
+ path_blocked = optional(string, "/login.html")
+ })
+ default = {}
+}
diff --git a/blueprints/third-party-solutions/openshift/tf/versions.tf b/blueprints/third-party-solutions/openshift/tf/versions.tf
index 286536a65..08492c6f9 100644
--- a/blueprints/third-party-solutions/openshift/tf/versions.tf
+++ b/blueprints/third-party-solutions/openshift/tf/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/default-versions.tf b/default-versions.tf
index 286536a65..08492c6f9 100644
--- a/default-versions.tf
+++ b/default-versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/diagram.svg b/diagram.svg
new file mode 100644
index 000000000..689adf24e
--- /dev/null
+++ b/diagram.svg
@@ -0,0 +1,293 @@
+
\ No newline at end of file
diff --git a/fast/README.md b/fast/README.md
index e35a48372..7459c870c 100644
--- a/fast/README.md
+++ b/fast/README.md
@@ -12,7 +12,7 @@ Fabric FAST was initially conceived to help enterprises quickly set up a GCP org
### Contracts and stages
-FAST uses the concept of stages, which individually perform precise tasks but, taken together, build a functional, ready-to-use GCP organization. More importantly, stages are modeled around the security boundaries that typically appear in mature organizations. This arrangement allows delegating ownership of each stage to the team responsible for the types of resources it manages. For example, as its name suggests, the networking stage sets up all the networking elements and is usually the responsibility of a dedicated networking team within the organization.
+FAST uses the concept of stages, which individually perform precise tasks but taken together build a functional, ready-to-use GCP organization. More importantly, stages are modeled around the security boundaries that typically appear in mature organizations. This arrangement allows delegating ownership of each stage to the team responsible for the types of resources it manages. For example, as its name suggests, the networking stage sets up all the networking elements and is usually the responsibility of a dedicated networking team within the organization.
From the perspective of FAST's overall design, stages also work as contacts or interfaces, defining a set of pre-requisites and inputs required to perform their designed task and generating outputs needed by other stages lower in the chain. The diagram below shows the relationships between stages.
@@ -20,7 +20,7 @@ From the perspective of FAST's overall design, stages also work as contacts or i
+
+
+
+
github_actions_secret · github_repository · github_repository_deploy_key · github_repository_file · tls_private_key |
+| [outputs.tf](./outputs.tf) | Module outputs. | |
+| [providers.tf](./providers.tf) | Provider configuration. | |
+| [variables.tf](./variables.tf) | Module variables. | |
+
+## Variables
+
+| name | description | type | required | default |
+|---|---|:---:|:---:|:---:|
+| [organization](variables.tf#L50) | GitHub organization. | string | ✓ | |
+| [commmit_config](variables.tf#L17) | Configure commit metadata. | object({…}) | | {} |
+| [modules_config](variables.tf#L28) | Configure access to repository module via key, and replacement for modules sources in stage repositories. | object({…}) | | null |
+| [repositories](variables.tf#L55) | Repositories to create. | map(object({…})) | | {} |
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| [clone](outputs.tf#L17) | Clone repository commands. | |
+
+
diff --git a/fast/extras/00-cicd-github/cicd-versions.tf b/fast/extras/0-cicd-github/cicd-versions.tf
similarity index 96%
rename from fast/extras/00-cicd-github/cicd-versions.tf
rename to fast/extras/0-cicd-github/cicd-versions.tf
index 09f544cba..830f1e48a 100644
--- a/fast/extras/00-cicd-github/cicd-versions.tf
+++ b/fast/extras/0-cicd-github/cicd-versions.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/fast/extras/00-cicd-github/github_token.png b/fast/extras/0-cicd-github/github_token.png
similarity index 100%
rename from fast/extras/00-cicd-github/github_token.png
rename to fast/extras/0-cicd-github/github_token.png
diff --git a/fast/extras/00-cicd-github/main.tf b/fast/extras/0-cicd-github/main.tf
similarity index 72%
rename from fast/extras/00-cicd-github/main.tf
rename to fast/extras/0-cicd-github/main.tf
index ac6028c17..d91ab970c 100644
--- a/fast/extras/00-cicd-github/main.tf
+++ b/fast/extras/0-cicd-github/main.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,9 +15,6 @@
*/
locals {
- _modules_repository = [
- for k, v in var.repositories : local.repositories[k] if v.has_modules
- ]
_repository_files = flatten([
for k, v in var.repositories : [
for f in concat(
@@ -30,12 +27,12 @@ locals {
}
] if v.populate_from != null
])
- modules_ref = var.modules_ref == null ? "" : "?ref=${var.modules_ref}"
- modules_repository = (
- length(local._modules_repository) > 0
- ? local._modules_repository.0
- : null
+ modules_ref = (
+ try(var.modules_config.source_ref, null) == null
+ ? ""
+ : "?ref=${var.modules_config.source_ref}"
)
+ modules_repo = try(var.modules_config.repository_name, null)
repositories = {
for k, v in var.repositories :
k => v.create_options == null ? k : github_repository.default[k].name
@@ -56,6 +53,15 @@ locals {
name = "templates/providers.tf.tpl"
}
if v.populate_from != null
+ },
+ {
+ for k, v in var.repositories :
+ "${k}/templates/workflow-github.yaml" => {
+ repository = k
+ file = "../../assets/templates/workflow-github.yaml"
+ name = "templates/workflow-github.yaml"
+ }
+ if v.populate_from != null
}
)
}
@@ -96,41 +102,49 @@ resource "github_repository" "default" {
}
resource "tls_private_key" "default" {
- count = local.modules_repository != null ? 1 : 0
algorithm = "ED25519"
}
resource "github_repository_deploy_key" "default" {
- count = local.modules_repository == null ? 0 : 1
+ count = (
+ try(var.modules_config.key_config.create_key, null) == true ? 1 : 0
+ )
title = "Modules repository access"
- repository = local.modules_repository
- key = tls_private_key.default.0.public_key_openssh
- read_only = true
+ repository = local.modules_repo
+ key = (
+ try(var.modules_config.key_config.keypair_path, null) == null
+ ? tls_private_key.default.public_key_openssh
+ : file(pathexpand("${var.modules_config.key_config.keypair_path}.pub"))
+ )
+ read_only = true
}
resource "github_actions_secret" "default" {
- for_each = local.modules_repository == null ? {} : {
- for k, v in local.repositories :
- k => v if k != local.modules_repository
- }
- repository = each.key
- secret_name = "CICD_MODULES_KEY"
- plaintext_value = tls_private_key.default.0.private_key_openssh
+ for_each = (
+ try(var.modules_config.key_config.create_secrets, null) == true
+ ? local.repositories
+ : {}
+ )
+ repository = each.key
+ secret_name = "CICD_MODULES_KEY"
+ plaintext_value = (
+ try(var.modules_config.key_config.keypair_path, null) == null
+ ? tls_private_key.default.private_key_openssh
+ : file(pathexpand("${var.modules_config.key_config.keypair_path}"))
+ )
}
resource "github_repository_file" "default" {
- for_each = (
- local.modules_repository == null ? {} : local.repository_files
- )
+ for_each = local.modules_repo == null ? {} : local.repository_files
repository = local.repositories[each.value.repository]
branch = "main"
file = each.value.name
content = (
- endswith(each.value.name, ".tf") && local.modules_repository != null
+ endswith(each.value.name, ".tf") && local.modules_repo != null
? replace(
file(each.value.file),
"/source\\s*=\\s*\"../../../modules/([^/\"]+)\"/",
- "source = \"git@github.com:${var.organization}/${local.modules_repository}.git//$1${local.modules_ref}\"" # "
+ "source = \"git@github.com:${local.modules_repo}.git//$1${local.modules_ref}\"" # "
)
: file(each.value.file)
)
diff --git a/fast/extras/00-cicd-github/outputs.tf b/fast/extras/0-cicd-github/outputs.tf
similarity index 96%
rename from fast/extras/00-cicd-github/outputs.tf
rename to fast/extras/0-cicd-github/outputs.tf
index cb580e1fe..61b5ffbc7 100644
--- a/fast/extras/00-cicd-github/outputs.tf
+++ b/fast/extras/0-cicd-github/outputs.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/fast/extras/00-cicd-github/providers.tf b/fast/extras/0-cicd-github/providers.tf
similarity index 95%
rename from fast/extras/00-cicd-github/providers.tf
rename to fast/extras/0-cicd-github/providers.tf
index 29be30ae9..a7ccb32d4 100644
--- a/fast/extras/00-cicd-github/providers.tf
+++ b/fast/extras/0-cicd-github/providers.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/fast/extras/00-cicd-github/variables.tf b/fast/extras/0-cicd-github/variables.tf
similarity index 75%
rename from fast/extras/00-cicd-github/variables.tf
rename to fast/extras/0-cicd-github/variables.tf
index 0d9cb7fd6..8e5d0832f 100644
--- a/fast/extras/00-cicd-github/variables.tf
+++ b/fast/extras/0-cicd-github/variables.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,10 +25,26 @@ variable "commmit_config" {
nullable = false
}
-variable "modules_ref" {
- description = "Optional git ref used in module sources."
- type = string
- default = null
+variable "modules_config" {
+ description = "Configure access to repository module via key, and replacement for modules sources in stage repositories."
+ type = object({
+ repository_name = string
+ source_ref = optional(string)
+ key_config = optional(object({
+ create_key = optional(bool, false)
+ create_secrets = optional(bool, false)
+ keypair_path = optional(string)
+ }), {})
+ })
+ default = null
+ validation {
+ condition = (
+ var.modules_config == null
+ ||
+ try(var.modules_config.repository_name, null) != null
+ )
+ error_message = "Modules configuration requires a modules repository name."
+ }
}
variable "organization" {
@@ -63,7 +79,6 @@ variable "repositories" {
}), {})
visibility = optional(string, "private")
}))
- has_modules = optional(bool, false)
populate_from = optional(string)
}))
default = {}
diff --git a/fast/extras/00-cicd-github/README.md b/fast/extras/00-cicd-github/README.md
deleted file mode 100644
index 52c322a33..000000000
--- a/fast/extras/00-cicd-github/README.md
+++ /dev/null
@@ -1,105 +0,0 @@
-# FAST GitHub repository management
-
-This small extra stage allows creation and management of GitHub repositories used to host FAST stage code, including initial population of files and rewriting of module sources.
-
-This stage is designed for quick repository creation in a GitHub organization, and is not suited for medium or long-term repository management especially if you enable initial population of files.
-
-## Initial population caveats
-
-Initial file population of repositories is controlled via the `populate_from` attribute, and needs a bit of care:
-
-- never run this stage with the same variables used for population once the repository starts being used, as **Terraform will manage file state and revert any changes at each apply**, which is probably not what you want.
-- initial population of the modules repository is discouraged, as the number of resulting files Terraform needs to manage is very close to the GitHub hourly limit for their API, it's much easier to populate modules via regular git commands
-
-The scenario for which this stage has been designed is one-shot creation and/or population of stage repositories, running it multiple times with different variables and Terraform states if incremental creation is needed for subsequent FAST stages (e.g. GKE, data platform, etc.).
-
-Once initial population is done, you need to manually push to the repository
-
-- the `.tfvars` file with custom variable values for your stages
-- the workflow configuration file generated by FAST stages
-
-## GitHub provider credentials
-
-A [GitHub token](https://github.com/settings/tokens) is needed to authenticate against their API. The token needs organization-level permissions, like shown in this screenshot:
-
-
-
-
github_actions_secret · github_repository · github_repository_deploy_key · github_repository_file · tls_private_key |
-| [outputs.tf](./outputs.tf) | Module outputs. | |
-| [providers.tf](./providers.tf) | Provider configuration. | |
-| [variables.tf](./variables.tf) | Module variables. | |
-
-## Variables
-
-| name | description | type | required | default |
-|---|---|:---:|:---:|:---:|
-| [organization](variables.tf#L34) | GitHub organization. | string | ✓ | |
-| [commmit_config](variables.tf#L17) | Configure commit metadata. | object({…}) | | {} |
-| [modules_ref](variables.tf#L28) | Optional git ref used in module sources. | string | | null |
-| [repositories](variables.tf#L39) | Repositories to create. | map(object({…})) | | {} |
-
-## Outputs
-
-| name | description | sensitive |
-|---|---|:---:|
-| [clone](outputs.tf#L17) | Clone repository commands. | |
-
-
diff --git a/fast/extras/README.md b/fast/extras/README.md
index 121fa4b04..9213224cd 100644
--- a/fast/extras/README.md
+++ b/fast/extras/README.md
@@ -2,4 +2,4 @@
This folder contains additional helper stages for FAST, which can be used to simplify specific operational tasks:
-- [GitHub repository management](./00-cicd-github/)
+- [GitHub repository management](./0-cicd-github/)
diff --git a/fast/stage-links.sh b/fast/stage-links.sh
new file mode 100755
index 000000000..52c9e5ae6
--- /dev/null
+++ b/fast/stage-links.sh
@@ -0,0 +1,119 @@
+#!/bin/bash
+# Copyright 2023 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.
+
+if [ $# -eq 0 ]; then
+ echo "Error: no folder or GCS bucket specified. Use -h or --help for usage."
+ exit 1
+fi
+
+if [[ "$1" == "-h" || "$1" == "--help" ]]; then
+ cat <+ additive, • conditional.
+
+## Organization [org_id #0]
+
+| members | roles |
+|---|---|
+|tn0-admins+•+|
+|tn0-gke-dev-0+•|
+|tn0-gke-prod-0+•|
+|tn0-networking-0+•|
+|tn0-pf-dev-0+•|
+|tn0-pf-prod-0+•|
+|tn0-resman-0+•|
+|tn0-sandbox-0+•|
+|tn0-security-0+•|
+|tn0-teams-0+•|
+
+## Folder test tenant 0 [#1]
+
+| members | roles |
+|---|---|
+|tn0-admins+|
+
+## Project tn0-audit-logs-0
+
+| members | roles |
+|---|---|
+|f260055713332-284719+•|
+|prod-resman-0+|
+|prod-resman-0iam-service-account | google_organization_iam_member |
+| [automation.tf](./automation.tf) | Tenant automation project and resources. | gcs · iam-service-account · project | |
+| [billing.tf](./billing.tf) | Billing roles for standalone billing accounts. | | google_billing_account_iam_member |
+| [cicd.tf](./cicd.tf) | Workload Identity Federation configurations for CI/CD. | iam-service-account · source-repository | |
+| [identity-providers.tf](./identity-providers.tf) | Workload Identity Federation provider definitions. | | google_iam_workload_identity_pool · google_iam_workload_identity_pool_provider |
+| [log-export.tf](./log-export.tf) | Audit log project and sink. | bigquery-dataset · gcs · logging-bucket · project · pubsub | |
+| [main.tf](./main.tf) | Module-level locals and resources. | folder | |
+| [organization.tf](./organization.tf) | Organization tag and conditional IAM grant. | organization | google_organization_iam_member · google_tags_tag_value_iam_member |
+| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | local_file |
+| [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | google_storage_bucket_object |
+| [outputs.tf](./outputs.tf) | Module outputs. | | |
+| [variables.tf](./variables.tf) | Module variables. | | |
+
+## Variables
+
+| name | description | type | required | default | producer |
+|---|---|:---:|:---:|:---:|:---:|
+| [automation](variables.tf#L20) | Automation resources created by the organization-level bootstrap stage. | object({…}) | ✓ | | 0-bootstrap |
+| [billing_account](variables.tf#L38) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap |
+| [organization](variables.tf#L194) | Organization details. | object({…}) | ✓ | | 0-bootstrap |
+| [prefix](variables.tf#L210) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap |
+| [tag_keys](variables.tf#L233) | Organization tag keys. | object({…}) | ✓ | | 1-resman |
+| [tag_names](variables.tf#L244) | Customized names for resource management tags. | object({…}) | ✓ | | 1-resman |
+| [tag_values](variables.tf#L255) | Organization resource management tag values. | map(string) | ✓ | | 1-resman |
+| [tenant_config](variables.tf#L262) | Tenant configuration. Short name must be 4 characters or less. | object({…}) | ✓ | | |
+| [cicd_repositories](variables.tf#L51) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | |
+| [custom_roles](variables.tf#L97) | Custom roles defined at the organization level, in key => id format. | object({…}) | | null | 0-bootstrap |
+| [fast_features](variables.tf#L107) | Selective control for top-level FAST features. | object({…}) | | {} | 0-bootstrap |
+| [federated_identity_providers](variables.tf#L121) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | |
+| [group_iam](variables.tf#L135) | Tenant-level custom group IAM settings in group => [roles] format. | map(list(string)) | | {} | |
+| [iam](variables.tf#L141) | Tenant-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | |
+| [iam_additive](variables.tf#L147) | Tenant-level custom IAM settings in role => [principal] format for non-authoritative bindings. | map(list(string)) | | {} | |
+| [locations](variables.tf#L153) | Optional locations for GCS, BigQuery, and logging buckets created here. These are the defaults set at the organization level, and can be overridden via the tenant config variable. | object({…}) | | {…} | 0-bootstrap |
+| [log_sinks](variables.tf#L173) | Tenant-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | |
+| [outputs_location](variables.tf#L204) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | |
+| [project_parent_ids](variables.tf#L220) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the tenant folder as parent. | object({…}) | | {…} | |
+| [test_principal](variables.tf#L302) | Used when testing to bypass the data source returning the current identity. | string | | null | |
+
+## Outputs
+
+| name | description | sensitive | consumers |
+|---|---|:---:|---|
+| [cicd_workflows](outputs.tf#L102) | CI/CD workflows for tenant bootstrap and resource management stages. | ✓ | |
+| [federated_identity](outputs.tf#L108) | Workload Identity Federation pool and providers. | | |
+| [provider](outputs.tf#L118) | Terraform provider file for tenant resource management stage. | ✓ | stage-01 |
+| [tenant_resources](outputs.tf#L125) | Tenant-level resources. | | |
+| [tfvars](outputs.tf#L136) | Terraform variable files for the following tenant stages. | ✓ | |
+
+
diff --git a/fast/stages-multitenant/0-bootstrap-tenant/automation-sas.tf b/fast/stages-multitenant/0-bootstrap-tenant/automation-sas.tf
new file mode 100644
index 000000000..cae14093e
--- /dev/null
+++ b/fast/stages-multitenant/0-bootstrap-tenant/automation-sas.tf
@@ -0,0 +1,135 @@
+/**
+ * Copyright 2023 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 Tenant automation stage 2 and 3 service accounts.
+
+locals {
+ branch_sas = {
+ dp-dev = {
+ condition = join(" && ", [
+ "resource.matchTag('${local.tag_keys.context}', 'data')",
+ "resource.matchTag('${local.tag_keys.environment}', 'development')"
+ ])
+ description = "data platform dev"
+ flag = "data_platform"
+ }
+ dp-prod = {
+ condition = join(" && ", [
+ "resource.matchTag('${local.tag_keys.context}', 'data')",
+ "resource.matchTag('${local.tag_keys.environment}', 'production')"
+ ])
+ description = "data platform prod"
+ flag = "data_platform"
+ }
+ gke-dev = {
+ condition = join(" && ", [
+ "resource.matchTag('${local.tag_keys.context}', 'gke')",
+ "resource.matchTag('${local.tag_keys.environment}', 'development')"
+ ])
+ description = "GKE dev"
+ flag = "gke"
+ }
+ gke-prod = {
+ condition = join(" && ", [
+ "resource.matchTag('${local.tag_keys.context}', 'gke')",
+ "resource.matchTag('${local.tag_keys.environment}', 'production')"
+ ])
+ description = "GKE prod"
+ flag = "gke"
+ }
+ networking = {
+ condition = "resource.matchTag('${local.tag_keys.context}', 'networking')"
+ description = "networking"
+ flag = "-"
+ }
+ pf-dev = {
+ condition = "resource.matchTag('${local.tag_keys.environment}', 'development')"
+ description = "project factory dev"
+ flag = "project_factory"
+ }
+ pf-prod = {
+ condition = "resource.matchTag('${local.tag_keys.environment}', 'production')"
+ description = "project factory prod"
+ flag = "project_factory"
+ }
+ sandbox = {
+ condition = "resource.matchTag('${local.tag_keys.context}', 'sandbox')"
+ description = "sandbox"
+ flag = "sandbox"
+ }
+ security = {
+ condition = "resource.matchTag('${local.tag_keys.context}', 'security')"
+ description = "security"
+ flag = "-"
+ }
+ teams = {
+ condition = "resource.matchTag('${local.tag_keys.context}', 'teams')"
+ description = "teams"
+ flag = "teams"
+ }
+ }
+}
+
+module "automation-tf-resman-sa-stage2-3" {
+ source = "../../../modules/iam-service-account"
+ for_each = {
+ for k, v in local.branch_sas :
+ k => v if lookup(local.fast_features, v.flag, true)
+ }
+ project_id = module.automation-project.project_id
+ name = "${each.key}-0"
+ display_name = "Terraform ${each.value.description} service account."
+ prefix = local.prefix
+ iam_billing_roles = !var.billing_account.is_org_level ? {
+ (var.billing_account.id) = [
+ "roles/billing.user", "roles/billing.costsManager"
+ ]
+ } : {}
+ iam_organization_roles = var.billing_account.is_org_level ? {
+ (var.organization.id) = [
+ "roles/billing.user", "roles/billing.costsManager"
+ ]
+ } : {}
+}
+
+# assign org policy admin with a tag-based condition to stage 2 and 3 SAs
+
+resource "google_organization_iam_member" "org_policy_admin_stage2_3" {
+ for_each = {
+ for k, v in module.automation-tf-resman-sa-stage2-3 : k => v.iam_email
+ }
+ org_id = var.organization.id
+ role = "roles/orgpolicy.policyAdmin"
+ member = each.value
+ condition {
+ title = "org_policy_tag_${var.tenant_config.short_name}_${each.key}_scoped"
+ description = join("", [
+ "Org policy tag scoped grant for tenant ${var.tenant_config.short_name} ",
+ local.branch_sas[each.key].description
+ ])
+ expression = join(" && ", [
+ local.iam_tenant_condition, local.branch_sas[each.key].condition
+ ])
+ }
+}
+
+# assign custom tenant network admin role to networking SA
+
+resource "google_organization_iam_member" "tenant_network_admin" {
+ org_id = var.organization.id
+ role = var.custom_roles.tenant_network_admin
+ member = module.automation-tf-resman-sa-stage2-3["networking"].iam_email
+}
diff --git a/fast/stages-multitenant/0-bootstrap-tenant/automation.tf b/fast/stages-multitenant/0-bootstrap-tenant/automation.tf
new file mode 100644
index 000000000..9684e7ca3
--- /dev/null
+++ b/fast/stages-multitenant/0-bootstrap-tenant/automation.tf
@@ -0,0 +1,141 @@
+/**
+ * Copyright 2023 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 Tenant automation project and resources.
+
+module "automation-project" {
+ source = "../../../modules/project"
+ billing_account = var.billing_account.id
+ name = "iac-core-0"
+ parent = coalesce(
+ var.project_parent_ids.automation,
+ module.tenant-folder.id
+ )
+ prefix = local.prefix
+ # human (groups) IAM bindings
+ group_iam = {
+ (local.groups.gcp-admins) = [
+ "roles/iam.serviceAccountAdmin",
+ "roles/iam.serviceAccountTokenCreator",
+ ]
+ (local.groups.gcp-admins) = [
+ "roles/iam.serviceAccountTokenCreator",
+ "roles/iam.workloadIdentityPoolAdmin"
+ ]
+ }
+ # machine (service accounts) IAM bindings
+ iam = {
+ "roles/owner" = [
+ module.automation-tf-resman-sa.iam_email,
+ "serviceAccount:${local.resman_sa}"
+ ]
+ "roles/cloudbuild.builds.editor" = [
+ module.automation-tf-resman-sa.iam_email
+ ]
+ "roles/iam.serviceAccountAdmin" = [
+ module.automation-tf-resman-sa.iam_email
+ ]
+ "roles/iam.workloadIdentityPoolAdmin" = [
+ module.automation-tf-resman-sa.iam_email
+ ]
+ "roles/source.admin" = [
+ module.automation-tf-resman-sa.iam_email
+ ]
+ "roles/storage.admin" = [
+ module.automation-tf-resman-sa.iam_email
+ ]
+ }
+ services = [
+ "accesscontextmanager.googleapis.com",
+ "bigquery.googleapis.com",
+ "bigqueryreservation.googleapis.com",
+ "bigquerystorage.googleapis.com",
+ "billingbudgets.googleapis.com",
+ "cloudbilling.googleapis.com",
+ "cloudbuild.googleapis.com",
+ "cloudkms.googleapis.com",
+ "cloudresourcemanager.googleapis.com",
+ "container.googleapis.com",
+ "compute.googleapis.com",
+ "container.googleapis.com",
+ "essentialcontacts.googleapis.com",
+ "iam.googleapis.com",
+ "iamcredentials.googleapis.com",
+ "orgpolicy.googleapis.com",
+ "pubsub.googleapis.com",
+ "servicenetworking.googleapis.com",
+ "serviceusage.googleapis.com",
+ "sourcerepo.googleapis.com",
+ "stackdriver.googleapis.com",
+ "storage-component.googleapis.com",
+ "storage.googleapis.com",
+ "sts.googleapis.com"
+ ]
+}
+
+# output files bucket
+
+module "automation-tf-output-gcs" {
+ source = "../../../modules/gcs"
+ project_id = module.automation-project.project_id
+ name = "iac-core-outputs-0"
+ prefix = local.prefix
+ location = local.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+}
+
+# resource management stage bucket and service account
+
+module "automation-tf-resman-gcs" {
+ source = "../../../modules/gcs"
+ project_id = module.automation-project.project_id
+ name = "iac-core-resman-0"
+ prefix = local.prefix
+ location = local.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [module.automation-tf-resman-sa.iam_email]
+ }
+}
+
+module "automation-tf-resman-sa" {
+ source = "../../../modules/iam-service-account"
+ project_id = module.automation-project.project_id
+ name = "resman-0"
+ display_name = "Terraform stage 1 resman service account."
+ prefix = local.prefix
+ # allow SA used by CI/CD workflow to impersonate this SA
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.automation-tf-cicd-sa-resman["0"].iam_email, null)
+ ])
+ }
+ iam_billing_roles = !var.billing_account.is_org_level ? {
+ (var.billing_account.id) = [
+ "roles/billing.admin", "roles/billing.costsManager"
+ ]
+ } : {}
+ iam_organization_roles = var.billing_account.is_org_level ? {
+ (var.organization.id) = [
+ "roles/billing.admin", "roles/billing.costsManager"
+ ]
+ } : {}
+ iam_storage_roles = {
+ (module.automation-tf-output-gcs.name) = ["roles/storage.admin"]
+ }
+}
diff --git a/fast/stages-multitenant/0-bootstrap-tenant/billing.tf b/fast/stages-multitenant/0-bootstrap-tenant/billing.tf
new file mode 100644
index 000000000..77c26b919
--- /dev/null
+++ b/fast/stages-multitenant/0-bootstrap-tenant/billing.tf
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2023 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 Billing roles for standalone billing accounts.
+
+# service account billing roles are in the SA module in automation.tf
+
+resource "google_billing_account_iam_member" "billing_ext_admin" {
+ for_each = toset(var.billing_account.is_org_level ? [] : [
+ "group:${local.groups.gcp-admins}",
+ module.automation-tf-resman-sa.iam_email
+ ])
+ billing_account_id = var.billing_account.id
+ role = "roles/billing.admin"
+ member = each.key
+}
+
+resource "google_billing_account_iam_member" "billing_ext_cost_manager" {
+ for_each = toset(var.billing_account.is_org_level ? [] : [
+ "group:${local.groups.gcp-admins}",
+ module.automation-tf-resman-sa.iam_email
+ ])
+ billing_account_id = var.billing_account.id
+ role = "roles/billing.costsManager"
+ member = each.key
+}
diff --git a/fast/stages-multitenant/0-bootstrap-tenant/cicd.tf b/fast/stages-multitenant/0-bootstrap-tenant/cicd.tf
new file mode 100644
index 000000000..a25215af2
--- /dev/null
+++ b/fast/stages-multitenant/0-bootstrap-tenant/cicd.tf
@@ -0,0 +1,223 @@
+/**
+ * Copyright 2023 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 Workload Identity Federation configurations for CI/CD.
+
+locals {
+ _file_prefix = "tenants/${var.tenant_config.short_name}"
+ # derive identity pool names from identity providers for easy reference
+ cicd_identity_pools = {
+ for k, v in local.cicd_identity_providers :
+ k => split("/providers/", v.name)[0]
+ }
+ # merge org-level and tenant-level identity providers
+ cicd_identity_providers = merge(
+ var.automation.federated_identity_providers,
+ {
+ for k, v in google_iam_workload_identity_pool_provider.default :
+ k => {
+ issuer = local.identity_providers[k].issuer
+ issuer_uri = local.identity_providers[k].issuer_uri
+ name = v.name
+ principal_tpl = local.identity_providers[k].principal_tpl
+ principalset_tpl = local.identity_providers[k].principalset_tpl
+ }
+ })
+ # filter CI/CD repositories to only keep valid ones
+ cicd_repositories = {
+ for k, v in coalesce(var.cicd_repositories, {}) : k => v
+ if(
+ v != null
+ &&
+ (
+ try(v.type, null) == "sourcerepo"
+ ||
+ contains(
+ keys(local.cicd_identity_providers),
+ coalesce(try(v.identity_provider, null), ":")
+ )
+ )
+ &&
+ fileexists(
+ format("${path.module}/templates/workflow-%s.yaml", try(v.type, ""))
+ )
+ )
+ }
+}
+
+# tenant bootstrap runs in the org scope and uses top-level automation project
+
+module "automation-tf-cicd-repo-bootstrap" {
+ source = "../../../modules/source-repository"
+ for_each = {
+ for k, v in local.cicd_repositories : 0 => v
+ if k == "bootstrap" && try(v.type, null) == "sourcerepo"
+ }
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = [
+ local.resman_sa
+ ]
+ "roles/source.reader" = [
+ module.automation-tf-cicd-sa-bootstrap["0"].iam_email
+ ]
+ }
+ triggers = {
+ "fast-${var.tenant_config.short_name}-0-bootstrap" = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
+ service_account = module.automation-tf-cicd-sa-bootstrap["0"].id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+}
+
+module "automation-tf-cicd-sa-bootstrap" {
+ source = "../../../modules/iam-service-account"
+ for_each = {
+ for k, v in local.cicd_repositories : 0 => v
+ if k == "bootstrap" && try(v.type, null) != null
+ }
+ project_id = var.automation.project_id
+ name = "bootstrap-1"
+ display_name = "Terraform CI/CD ${var.tenant_config.short_name} bootstrap."
+ prefix = local.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {}
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
+
+module "automation-tf-org-resman-sa" {
+ source = "../../../modules/iam-service-account"
+ for_each = {
+ for k, v in local.cicd_repositories : 0 => v
+ if k == "bootstrap" && try(v.type, null) != null
+ }
+ project_id = var.automation.project_id
+ name = local.resman_sa
+ service_account_create = false
+ iam_additive = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.automation-tf-cicd-sa-bootstrap["0"].iam_email, null)
+ ])
+ }
+}
+
+# tenant resman runs in the tenant scope and uses its own automation project
+
+module "automation-tf-cicd-repo-resman" {
+ source = "../../../modules/source-repository"
+ for_each = {
+ for k, v in local.cicd_repositories : 0 => v
+ if k == "resman" && try(v.type, null) == "sourcerepo"
+ }
+ project_id = module.automation-project.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = [
+ module.automation-tf-resman-sa.iam_email
+ ]
+ "roles/source.reader" = [
+ module.automation-tf-cicd-sa-resman["0"].iam_email
+ ]
+ }
+ triggers = {
+ fast-1-resman = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
+ service_account = module.automation-tf-cicd-sa-resman["0"].id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+}
+
+module "automation-tf-cicd-sa-resman" {
+ source = "../../../modules/iam-service-account"
+ for_each = {
+ for k, v in local.cicd_repositories : 0 => v
+ if k == "resman" && try(v.type, null) != null
+ }
+ project_id = module.automation-project.project_id
+ name = "resman-1"
+ display_name = "Terraform CI/CD resman."
+ prefix = local.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {}
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (module.automation-project.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (module.automation-tf-output-gcs.name) = ["roles/storage.objectViewer"]
+ }
+}
diff --git a/fast/stages-multitenant/0-bootstrap-tenant/diagram.svg b/fast/stages-multitenant/0-bootstrap-tenant/diagram.svg
new file mode 100644
index 000000000..4090c7b09
--- /dev/null
+++ b/fast/stages-multitenant/0-bootstrap-tenant/diagram.svg
@@ -0,0 +1,597 @@
+
++ additive, • conditional.
+
+## Folder development [#0]
+
+| members | roles |
+|---|---|
+|tn0-gke-dev-0++|
+|tn0-security-0+|
diff --git a/fast/stages-multitenant/1-resman-tenant/README.md b/fast/stages-multitenant/1-resman-tenant/README.md
new file mode 100644
index 000000000..ae42fc305
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/README.md
@@ -0,0 +1,184 @@
+# Tenant resource management
+
+This stage is run for a specific tenant after [tenant bootstrap](../0-bootstrap-tenant/) has successfully created initial resources for the tenant, which is then decoupled from the organization.
+
+It is logically equivalent and almost identical in code to the corresponding [organization resource management stage](../../stages/1-resman/), with a few notable differences:
+
+- the hierarchy is rooted in the tenant top-level folder instead of the organization
+- there's no management of tag values and keys since they organization-level resources (it could be implemented for tenant-specific tags if the need arises)
+- automation service accounts for subsequent stages are configured but not created here (tenant-level bootstrap creates them and assigns organization-level permissions)
+
+The stage runs with a dedicated service account for the tenant, which has no permissions at the organization level except for billing and organization policies, constrained by a condition on the tenant tag.
+
+The following diagram is a high level reference of what this stage manages, showing one hypothetical tenant (additional tenants require additional instances of this stage being deployed):
+
+```mermaid
+%%{init: {'theme':'base'}}%%
+classDiagram
+ Tenant_root~📁~ -- tn0_automation
+ Tenant_root~📁~ -- Networking~📁~
+ Tenant_root~📁~ -- Security~📁~
+ Tenant_root~📁~ -- Data_Platform~📁~
+ Data_Platform~📁~ -- DP_Dev~📁~
+ Data_Platform~📁~ -- DP_Prod~📁~
+ Tenant_root~📁~ -- GKE~📁~
+ GKE~📁~ -- GKE_Dev~📁~
+ GKE~📁~ -- GKE_Prod~📁~
+ Tenant_root~📁~ -- Teams~📁~
+ Teams~📁~ -- Team_0~📁~
+ Team_0~📁~ -- Team_0_Dev~📁~
+ Team_0~📁~ -- Team_0_Prod~📁~
+ Tenant_root~📁~ -- Sandbox~📁~
+ class Tenant_root~📁~ {
+ - IAM bindings()
+ - org policies()
+ }
+ class tn0_automation {
+ - GCS buckets
+ - IAM bindings()
+ }
+ class Data_Platform~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class DP_Dev~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class DP_Prod~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class GKE~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class GKE_Dev~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class GKE_Prod~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Networking~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Security~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Sandbox~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Teams~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Team_0~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Team_0_Dev~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Team_0_Prod~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+```
+
+As most of the features of this stage follow the same design and configurations of the [organization-level resource management stage](../../stages/1-resman/), we will only focus on the tenant-specific configuration in this document.
+
+## How to run this stage
+
+As mentioned above this stage is decoupled from organization-level stages: it uses a service account and state bucket from the tenant-specific automation project, and its tfvars and provider files are also tenant-specific.
+
+The `stage-links.sh` script can be used to get the commands needed for the provider and output files, just set the variable for the tenant shortname (the same one specified in the tenant bootstrap stage) and pass a single argument with your FAST output files folder path, or GCS bucket URI:
+
+```bash
+TENANT=tn0 ../../stage-links.sh ~/fast-config
+```
+
+The script output can be copy/pasted to a terminal:
+
+```bash
+# copy and paste the following commands for '1-resman-tenant'
+
+ln -s ~/fast-config/tenants/tn0/providers/1-resman-tenant-providers.tf ./
+ln -s ~/fast-config/tenants/tn0/tfvars/0-bootstrap-tenant.auto.tfvars.json ./
+```
+
+Once that is done, stage-level configuration variables are the same as the corresponding organization-level stage.
+
+### Running the stage
+
+Once the configuration is done just go through the usual `init/apply` cycle. On successful apply, a tfvars file specific for this tenant and a set of provider files will be created.
+
+
+
+
+## Files
+
+| name | description | modules | resources |
+|---|---|---|---|
+| [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder · gcs · iam-service-account | |
+| [branch-gke.tf](./branch-gke.tf) | GKE multitenant stage resources. | folder · gcs · iam-service-account | |
+| [branch-networking.tf](./branch-networking.tf) | Networking stage resources. | folder · gcs · iam-service-account | |
+| [branch-project-factory.tf](./branch-project-factory.tf) | Project factory stage resources. | gcs · iam-service-account | |
+| [branch-sandbox.tf](./branch-sandbox.tf) | Sandbox stage resources. | folder · gcs | |
+| [branch-security.tf](./branch-security.tf) | Security stage resources. | folder · gcs · iam-service-account | |
+| [branch-teams.tf](./branch-teams.tf) | Team stage resources. | folder · gcs · iam-service-account | |
+| [cicd-data-platform.tf](./cicd-data-platform.tf) | CI/CD resources for the data platform branch. | iam-service-account · source-repository | |
+| [cicd-gke.tf](./cicd-gke.tf) | CI/CD resources for the data platform branch. | iam-service-account · source-repository | |
+| [cicd-networking.tf](./cicd-networking.tf) | CI/CD resources for the networking branch. | iam-service-account · source-repository | |
+| [cicd-project-factory.tf](./cicd-project-factory.tf) | CI/CD resources for the teams branch. | iam-service-account · source-repository | |
+| [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | iam-service-account · source-repository | |
+| [main.tf](./main.tf) | Module-level locals and resources. | | |
+| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | local_file |
+| [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | google_storage_bucket_object |
+| [outputs.tf](./outputs.tf) | Module outputs. | | |
+| [root_node.tf](./root_node.tf) | Tenant root folder configuration. | folder | |
+| [variables.tf](./variables.tf) | Module variables. | | |
+
+## Variables
+
+| name | description | type | required | default | producer |
+|---|---|:---:|:---:|:---:|:---:|
+| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap |
+| [billing_account](variables.tf#L51) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap |
+| [organization](variables.tf#L206) | Organization details. | object({…}) | ✓ | | 0-bootstrap |
+| [prefix](variables.tf#L228) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap |
+| [root_node](variables.tf#L239) | Root folder node for the tenant, in folders/nnnnnn format. | string | ✓ | | |
+| [short_name](variables.tf#L244) | Short name used to identify the tenant. | string | ✓ | | |
+| [tags](variables.tf#L249) | Resource management tags. | object({…}) | ✓ | | |
+| [cicd_repositories](variables.tf#L64) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | |
+| [custom_roles](variables.tf#L146) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap |
+| [data_dir](variables.tf#L155) | Relative path for the folder storing configuration data. | string | | "data" | |
+| [fast_features](variables.tf#L161) | Selective control for top-level FAST features. | object({…}) | | {} | 0-0-bootstrap |
+| [groups](variables.tf#L175) | Group names to grant organization-level permissions. | object({…}) | | {} | 0-bootstrap |
+| [locations](variables.tf#L188) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | 0-bootstrap |
+| [organization_policy_data_path](variables.tf#L216) | Path for the data folder used by the organization policies factory. | string | | null | |
+| [outputs_location](variables.tf#L222) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | |
+| [team_folders](variables.tf#L267) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | |
+| [test_skip_data_sources](variables.tf#L277) | Used when testing to bypass data sources. | bool | | false | |
+
+## Outputs
+
+| name | description | sensitive | consumers |
+|---|---|:---:|---|
+| [cicd_repositories](outputs.tf#L189) | WIF configuration for CI/CD repositories. | | |
+| [dataplatform](outputs.tf#L203) | Data for the Data Platform stage. | | |
+| [gke_multitenant](outputs.tf#L219) | Data for the GKE multitenant stage. | | 03-gke-multitenant |
+| [networking](outputs.tf#L240) | Data for the networking stage. | | |
+| [project_factories](outputs.tf#L249) | Data for the project factories stage. | | |
+| [providers](outputs.tf#L264) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · 03-dataplatform · xx-sandbox · xx-teams |
+| [sandbox](outputs.tf#L271) | Data for the sandbox stage. | | xx-sandbox |
+| [security](outputs.tf#L285) | Data for the networking stage. | | 02-security |
+| [teams](outputs.tf#L295) | Data for the teams stage. | | |
+| [tfvars](outputs.tf#L307) | Terraform variable files for the following stages. | ✓ | |
+
+
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-data-platform.tf b/fast/stages-multitenant/1-resman-tenant/branch-data-platform.tf
new file mode 100644
index 000000000..3916d6358
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-data-platform.tf
@@ -0,0 +1,133 @@
+/**
+ * Copyright 2023 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 Data Platform stages resources.
+
+module "branch-dp-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.data_platform ? 1 : 0
+ parent = module.root-folder.id
+ name = "Data Platform"
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.context}/data"]
+ }
+}
+
+module "branch-dp-dev-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.data_platform ? 1 : 0
+ parent = module.branch-dp-folder.0.id
+ name = "Development"
+ group_iam = {}
+ iam = {
+ (local.custom_roles.service_project_network_admin) = [
+ local.automation_sas_iam.dp-dev
+ ]
+ # remove owner here and at project level if SA does not manage project resources
+ "roles/owner" = [local.automation_sas_iam.dp-dev]
+ "roles/logging.admin" = [local.automation_sas_iam.dp-dev]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.dp-dev]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.dp-dev]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.environment}/development"]
+ }
+}
+
+module "branch-dp-prod-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.data_platform ? 1 : 0
+ parent = module.branch-dp-folder.0.id
+ name = "Production"
+ group_iam = {}
+ iam = {
+ (local.custom_roles.service_project_network_admin) = [
+ local.automation_sas_iam.dp-prod
+ ]
+ # remove owner here and at project level if SA does not manage project resources
+ "roles/owner" = [local.automation_sas_iam.dp-prod]
+ "roles/logging.admin" = [local.automation_sas_iam.dp-prod]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.dp-prod]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.dp-prod]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.environment}/production"]
+ }
+}
+
+# automation service accounts and buckets
+
+module "branch-dp-dev-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.data_platform ? 1 : 0
+ project_id = var.automation.project_id
+ name = "dp-dev-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.branch-dp-dev-sa-cicd.0.iam_email, null)
+ ])
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-dp-prod-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.data_platform ? 1 : 0
+ project_id = var.automation.project_id
+ name = "dp-prod-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.branch-dp-prod-sa-cicd.0.iam_email, null)
+ ])
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-dp-dev-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.data_platform ? 1 : 0
+ project_id = var.automation.project_id
+ name = "dev-resman-dp-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.dp-dev]
+ }
+}
+
+module "branch-dp-prod-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.data_platform ? 1 : 0
+ project_id = var.automation.project_id
+ name = "prod-resman-dp-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.dp-prod]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-gke.tf b/fast/stages-multitenant/1-resman-tenant/branch-gke.tf
new file mode 100644
index 000000000..9ece810bb
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-gke.tf
@@ -0,0 +1,133 @@
+/**
+ * Copyright 2023 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 GKE multitenant stage resources.
+
+module "branch-gke-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.gke ? 1 : 0
+ parent = module.root-folder.id
+ name = "GKE"
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.context}/gke"]
+ }
+}
+
+module "branch-gke-dev-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.gke ? 1 : 0
+ parent = module.branch-gke-folder.0.id
+ name = "Development"
+ iam = {
+ "roles/owner" = [local.automation_sas_iam.gke-dev]
+ "roles/logging.admin" = [local.automation_sas_iam.gke-dev]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.gke-dev]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.gke-dev]
+ "roles/compute.xpnAdmin" = [local.automation_sas_iam.gke-dev]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.environment}/development"]
+ }
+}
+
+module "branch-gke-prod-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.gke ? 1 : 0
+ parent = module.branch-gke-folder.0.id
+ name = "Production"
+ iam = {
+ "roles/owner" = [local.automation_sas_iam.gke-prod]
+ "roles/logging.admin" = [local.automation_sas_iam.gke-prod]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.gke-prod]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.gke-prod]
+ "roles/compute.xpnAdmin" = [local.automation_sas_iam.gke-prod]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.environment}/production"]
+ }
+}
+
+module "branch-gke-dev-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.gke ? 1 : 0
+ project_id = var.automation.project_id
+ name = "gke-dev-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = concat(
+ (
+ local.groups.gcp-devops == null
+ ? []
+ : ["group:${local.groups.gcp-devops}"]
+ ),
+ compact([
+ try(module.branch-gke-dev-sa-cicd.0.iam_email, null)
+ ])
+ )
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-gke-prod-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.gke ? 1 : 0
+ project_id = var.automation.project_id
+ name = "gke-prod-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = concat(
+ (
+ local.groups.gcp-devops == null
+ ? []
+ : ["group:${local.groups.gcp-devops}"]
+ ),
+ compact([
+ try(module.branch-gke-prod-sa-cicd.0.iam_email, null)
+ ])
+ )
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-gke-dev-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.gke ? 1 : 0
+ project_id = var.automation.project_id
+ name = "dev-resman-gke-0"
+ prefix = var.prefix
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.gke-dev]
+ }
+}
+
+module "branch-gke-prod-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.gke ? 1 : 0
+ project_id = var.automation.project_id
+ name = "prod-resman-gke-0"
+ prefix = var.prefix
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.gke-prod]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-networking.tf b/fast/stages-multitenant/1-resman-tenant/branch-networking.tf
new file mode 100644
index 000000000..85490baf0
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-networking.tf
@@ -0,0 +1,107 @@
+/**
+ * Copyright 2023 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 stage resources.
+
+module "branch-network-folder" {
+ source = "../../../modules/folder"
+ parent = module.root-folder.id
+ name = "Networking"
+ group_iam = local.groups.gcp-network-admins == null ? {} : {
+ (local.groups.gcp-network-admins) = [
+ # add any needed roles for resources/services not managed via Terraform,
+ # or replace editor with ~viewer if no broad resource management needed
+ # e.g.
+ # "roles/compute.networkAdmin",
+ # "roles/dns.admin",
+ # "roles/compute.securityAdmin",
+ "roles/editor",
+ ]
+ }
+ iam = {
+ "roles/logging.admin" = [local.automation_sas_iam.networking]
+ "roles/owner" = [local.automation_sas_iam.networking]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.networking]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.networking]
+ "roles/compute.xpnAdmin" = [local.automation_sas_iam.networking]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.context}/networking"]
+ }
+}
+
+module "branch-network-prod-folder" {
+ source = "../../../modules/folder"
+ parent = module.branch-network-folder.id
+ name = "Production"
+ iam = {
+ (local.custom_roles.service_project_network_admin) = concat(
+ local.branch_optional_sa_lists.dp-prod,
+ local.branch_optional_sa_lists.gke-prod,
+ local.branch_optional_sa_lists.pf-prod,
+ )
+ }
+ tag_bindings = {
+ environment = var.tags.values["${var.tags.names.environment}/production"]
+ }
+}
+
+module "branch-network-dev-folder" {
+ source = "../../../modules/folder"
+ parent = module.branch-network-folder.id
+ name = "Development"
+ iam = {
+ (local.custom_roles.service_project_network_admin) = concat(
+ local.branch_optional_sa_lists.dp-dev,
+ local.branch_optional_sa_lists.gke-dev,
+ local.branch_optional_sa_lists.pf-dev,
+ )
+ }
+ tag_bindings = {
+ environment = var.tags.values["${var.tags.names.environment}/development"]
+ }
+}
+
+# automation service account and bucket
+
+module "branch-network-sa" {
+ source = "../../../modules/iam-service-account"
+ project_id = var.automation.project_id
+ name = "networking-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.branch-network-sa-cicd.0.iam_email, null)
+ ])
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-network-gcs" {
+ source = "../../../modules/gcs"
+ project_id = var.automation.project_id
+ name = "prod-resman-net-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.networking]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-project-factory.tf b/fast/stages-multitenant/1-resman-tenant/branch-project-factory.tf
new file mode 100644
index 000000000..2fa64bbc5
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-project-factory.tf
@@ -0,0 +1,79 @@
+/**
+ * Copyright 2023 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 Project factory stage resources.
+
+module "branch-pf-dev-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.project_factory ? 1 : 0
+ project_id = var.automation.project_id
+ name = "pf-dev-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.branch-pf-dev-sa-cicd.0.iam_email, null)
+ ])
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-pf-prod-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.project_factory ? 1 : 0
+ project_id = var.automation.project_id
+ name = "pf-prod-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.branch-pf-prod-sa-cicd.0.iam_email, null)
+ ])
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-pf-dev-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.project_factory ? 1 : 0
+ project_id = var.automation.project_id
+ name = "dev-resman-pf-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.pf-dev]
+ }
+}
+
+module "branch-pf-prod-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.project_factory ? 1 : 0
+ project_id = var.automation.project_id
+ name = "prod-resman-pf-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.pf-prod]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-sandbox.tf b/fast/stages-multitenant/1-resman-tenant/branch-sandbox.tf
new file mode 100644
index 000000000..6f3d526c8
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-sandbox.tf
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2023 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 Sandbox stage resources.
+
+module "branch-sandbox-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.sandbox ? 1 : 0
+ parent = module.root-folder.id
+ name = "Sandbox"
+ iam = {
+ "roles/logging.admin" = [local.automation_sas_iam.sandbox]
+ "roles/owner" = [local.automation_sas_iam.sandbox]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.sandbox]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.sandbox]
+ }
+ org_policies = {
+ "constraints/sql.restrictPublicIp" = { enforce = false }
+ "constraints/compute.vmExternalIpAccess" = { allow = { all = true } }
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.context}/sandbox"]
+ }
+}
+
+module "branch-sandbox-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.sandbox ? 1 : 0
+ project_id = var.automation.project_id
+ name = "dev-resman-sbox-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.sandbox]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-security.tf b/fast/stages-multitenant/1-resman-tenant/branch-security.tf
new file mode 100644
index 000000000..d7253cce1
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-security.tf
@@ -0,0 +1,76 @@
+/**
+ * Copyright 2023 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 Security stage resources.
+
+module "branch-security-folder" {
+ source = "../../../modules/folder"
+ parent = module.root-folder.id
+ name = "Security"
+ group_iam = local.groups.gcp-security-admins == null ? {} : {
+ (local.groups.gcp-security-admins) = [
+ # add any needed roles for resources/services not managed via Terraform,
+ # e.g.
+ # "roles/bigquery.admin",
+ # "roles/cloudasset.owner",
+ # "roles/cloudkms.admin",
+ # "roles/logging.admin",
+ # "roles/secretmanager.admin",
+ # "roles/storage.admin",
+ "roles/viewer"
+ ]
+ }
+ iam = {
+ "roles/logging.admin" = [local.automation_sas_iam.security]
+ "roles/owner" = [local.automation_sas_iam.security]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.security]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.security]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.context}/security"]
+ }
+}
+
+# automation service account and bucket
+
+module "branch-security-sa" {
+ source = "../../../modules/iam-service-account"
+ project_id = var.automation.project_id
+ name = "security-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.branch-security-sa-cicd.0.iam_email, null)
+ ])
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-security-gcs" {
+ source = "../../../modules/gcs"
+ project_id = var.automation.project_id
+ name = "prod-resman-sec-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.security]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-teams.tf b/fast/stages-multitenant/1-resman-tenant/branch-teams.tf
new file mode 100644
index 000000000..57f221104
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-teams.tf
@@ -0,0 +1,163 @@
+/**
+ * Copyright 2023 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 Team stage resources.
+
+# TODO(ludo): add support for CI/CD
+
+############### top-level Teams branch and automation resources ###############
+
+module "branch-teams-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.teams ? 1 : 0
+ parent = module.root-folder.id
+ name = "Teams"
+ iam = {
+ "roles/logging.admin" = [local.automation_sas_iam.teams]
+ "roles/owner" = [local.automation_sas_iam.teams]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.teams]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.teams]
+ "roles/compute.xpnAdmin" = [local.automation_sas_iam.teams]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.context}/teams"]
+ }
+}
+
+module "branch-teams-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.teams ? 1 : 0
+ project_id = var.automation.project_id
+ name = "teams-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-teams-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.teams ? 1 : 0
+ project_id = var.automation.project_id
+ name = "prod-resman-teams-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [module.branch-teams-sa.0.iam_email]
+ }
+}
+
+################## per-team folders and automation resources ##################
+
+module "branch-teams-team-folder" {
+ source = "../../../modules/folder"
+ for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {}
+ parent = module.branch-teams-folder.0.id
+ name = each.value.descriptive_name
+ iam = {
+ "roles/logging.admin" = [module.branch-teams-team-sa[each.key].iam_email]
+ "roles/owner" = [module.branch-teams-team-sa[each.key].iam_email]
+ "roles/resourcemanager.folderAdmin" = [module.branch-teams-team-sa[each.key].iam_email]
+ "roles/resourcemanager.projectCreator" = [module.branch-teams-team-sa[each.key].iam_email]
+ "roles/compute.xpnAdmin" = [module.branch-teams-team-sa[each.key].iam_email]
+ }
+ group_iam = each.value.group_iam == null ? {} : each.value.group_iam
+}
+
+module "branch-teams-team-sa" {
+ source = "../../../modules/iam-service-account"
+ for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {}
+ project_id = var.automation.project_id
+ name = "prod-teams-${each.key}-0"
+ display_name = "Terraform team ${each.key} service account."
+ prefix = var.prefix
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = (
+ each.value.impersonation_groups == null
+ ? []
+ : [for g in each.value.impersonation_groups : "group:${g}"]
+ )
+ }
+}
+
+module "branch-teams-team-gcs" {
+ source = "../../../modules/gcs"
+ for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {}
+ project_id = var.automation.project_id
+ name = "prod-teams-${each.key}-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [module.branch-teams-team-sa[each.key].iam_email]
+ }
+}
+
+# per-team environment folders where project factory SAs can create projects
+
+module "branch-teams-team-dev-folder" {
+ source = "../../../modules/folder"
+ for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {}
+ parent = module.branch-teams-team-folder[each.key].id
+ # naming: environment descriptive name
+ name = "Development"
+ # environment-wide human permissions on the whole teams environment
+ group_iam = {}
+ iam = {
+ (local.custom_roles.service_project_network_admin) = (
+ local.branch_optional_sa_lists.pf-dev
+ )
+ # remove owner here and at project level if SA does not manage project resources
+ "roles/owner" = local.branch_optional_sa_lists.pf-dev
+ "roles/logging.admin" = local.branch_optional_sa_lists.pf-dev
+ "roles/resourcemanager.folderAdmin" = local.branch_optional_sa_lists.pf-dev
+ "roles/resourcemanager.projectCreator" = local.branch_optional_sa_lists.pf-dev
+ }
+ tag_bindings = {
+ environment = try(
+ var.tags.values["${var.tags.names.environment}/development"], null
+ )
+ }
+}
+
+module "branch-teams-team-prod-folder" {
+ source = "../../../modules/folder"
+ for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {}
+ parent = module.branch-teams-team-folder[each.key].id
+ # naming: environment descriptive name
+ name = "Production"
+ # environment-wide human permissions on the whole teams environment
+ group_iam = {}
+ iam = {
+ (local.custom_roles.service_project_network_admin) = (
+ local.branch_optional_sa_lists.pf-prod
+ )
+ # remove owner here and at project level if SA does not manage project resources
+ "roles/owner" = local.branch_optional_sa_lists.pf-prod
+ "roles/logging.admin" = local.branch_optional_sa_lists.pf-prod
+ "roles/resourcemanager.folderAdmin" = local.branch_optional_sa_lists.pf-prod
+ "roles/resourcemanager.projectCreator" = local.branch_optional_sa_lists.pf-prod
+ }
+ tag_bindings = {
+ environment = try(
+ var.tags.values["${var.tags.names.environment}/production"], null
+ )
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-data-platform.tf b/fast/stages-multitenant/1-resman-tenant/cicd-data-platform.tf
new file mode 100644
index 000000000..704f45d78
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/cicd-data-platform.tf
@@ -0,0 +1,173 @@
+/**
+ * Copyright 2023 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 CI/CD resources for the data platform branch.
+
+# source repositories
+
+module "branch-dp-dev-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.data_platform_dev.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.data_platform_dev }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = local.branch_optional_sa_lists.dp-dev
+ "roles/source.reader" = compact([
+ try(module.branch-dp-dev-sa-cicd.0.iam_email, "")
+ ])
+ }
+ triggers = {
+ fast-03-dp-dev = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = [
+ "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
+ ]
+ service_account = module.branch-dp-dev-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-dp-dev-sa-cicd]
+}
+
+module "branch-dp-prod-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.data_platform_prod.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.data_platform_prod }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = local.branch_optional_sa_lists.dp-prod
+ "roles/source.reader" = [module.branch-dp-prod-sa-cicd.0.iam_email]
+ }
+ triggers = {
+ fast-03-dp-prod = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = [
+ "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
+ ]
+ service_account = module.branch-dp-prod-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-dp-prod-sa-cicd]
+}
+
+# SAs used by CI/CD workflows to impersonate automation SAs
+
+module "branch-dp-dev-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.data_platform_dev.name, null) != null
+ ? { 0 = local.cicd_repositories.data_platform_dev }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "dev-resman-dp-1"
+ display_name = "Terraform CI/CD data platform development service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
+
+module "branch-dp-prod-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.data_platform_prod.name, null) != null
+ ? { 0 = local.cicd_repositories.data_platform_prod }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "prod-resman-dp-1"
+ display_name = "Terraform CI/CD data platform production service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-gke.tf b/fast/stages-multitenant/1-resman-tenant/cicd-gke.tf
new file mode 100644
index 000000000..dfd035a51
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/cicd-gke.tf
@@ -0,0 +1,175 @@
+/**
+ * Copyright 2023 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 CI/CD resources for the data platform branch.
+
+# source repositories
+
+module "branch-gke-dev-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.gke_dev.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.gke_dev }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = compact([
+ try(module.branch-gke-dev-sa.0.iam_email, "")
+ ])
+ "roles/source.reader" = compact([
+ try(module.branch-gke-dev-sa-cicd.0.iam_email, "")
+ ])
+ }
+ triggers = {
+ fast-03-gke-dev = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = [
+ "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
+ ]
+ service_account = module.branch-gke-dev-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-gke-dev-sa-cicd]
+}
+
+module "branch-gke-prod-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.gke_prod.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.gke_prod }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = [module.branch-gke-prod-sa.0.iam_email]
+ "roles/source.reader" = [module.branch-gke-prod-sa-cicd.0.iam_email]
+ }
+ triggers = {
+ fast-03-gke-prod = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = [
+ "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
+ ]
+ service_account = module.branch-gke-prod-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-gke-prod-sa-cicd]
+}
+
+# SAs used by CI/CD workflows to impersonate automation SAs
+
+module "branch-gke-dev-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.gke_dev.name, null) != null
+ ? { 0 = local.cicd_repositories.gke_dev }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "dev-resman-gke-1"
+ display_name = "Terraform CI/CD GKE development service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
+
+module "branch-gke-prod-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.gke_prod.name, null) != null
+ ? { 0 = local.cicd_repositories.gke_prod }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "prod-resman-gke-1"
+ display_name = "Terraform CI/CD GKE production service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-networking.tf b/fast/stages-multitenant/1-resman-tenant/cicd-networking.tf
new file mode 100644
index 000000000..dbaf587d6
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/cicd-networking.tf
@@ -0,0 +1,94 @@
+/**
+ * Copyright 2023 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 CI/CD resources for the networking branch.
+
+# source repository
+
+module "branch-network-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.networking.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.networking }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = [module.branch-network-sa.iam_email]
+ "roles/source.reader" = [module.branch-network-sa-cicd.0.iam_email]
+ }
+ triggers = {
+ fast-02-networking = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
+ service_account = module.branch-network-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-network-sa-cicd]
+}
+
+# SA used by CI/CD workflows to impersonate automation SAs
+
+module "branch-network-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.networking.name, null) != null
+ ? { 0 = local.cicd_repositories.networking }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "prod-resman-net-1"
+ display_name = "Terraform CI/CD stage 2 networking service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-project-factory.tf b/fast/stages-multitenant/1-resman-tenant/cicd-project-factory.tf
new file mode 100644
index 000000000..4c46d8585
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/cicd-project-factory.tf
@@ -0,0 +1,191 @@
+/**
+ * Copyright 2023 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 CI/CD resources for the teams branch.
+
+# source repositories
+
+moved {
+ from = module.branch-teams-dev-pf-cicd-repo
+ to = module.branch-pf-dev-cicd-repo
+}
+
+module "branch-pf-dev-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.project_factory_dev.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.project_factory_dev }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = local.branch_optional_sa_lists.pf-dev
+ "roles/source.reader" = [module.branch-pf-dev-sa-cicd.0.iam_email]
+ }
+ triggers = {
+ fast-03-pf-dev = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = [
+ "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
+ ]
+ service_account = module.branch-pf-dev-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-pf-dev-sa-cicd]
+}
+
+moved {
+ from = module.branch-teams-prod-pf-cicd-repo
+ to = module.branch-pf-prod-cicd-repo
+}
+
+module "branch-pf-prod-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.project_factory_prod.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.project_factory_prod }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = local.branch_optional_sa_lists.pf-prod
+ "roles/source.reader" = [module.branch-pf-prod-sa-cicd.0.iam_email]
+ }
+ triggers = {
+ fast-03-pf-prod = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = [
+ "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
+ ]
+ service_account = module.branch-pf-prod-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-pf-prod-sa-cicd]
+}
+
+# SAs used by CI/CD workflows to impersonate automation SAs
+
+moved {
+ from = module.branch-teams-dev-pf-sa-cicd
+ to = module.branch-pf-dev-sa-cicd
+}
+
+module "branch-pf-dev-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.project_factory_dev.name, null) != null
+ ? { 0 = local.cicd_repositories.project_factory_dev }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "dev-pf-resman-pf-1"
+ display_name = "Terraform CI/CD project factory development service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
+
+moved {
+ from = module.branch-teams-prod-pf-sa-cicd
+ to = module.branch-pf-prod-sa-cicd
+}
+
+module "branch-pf-prod-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.project_factory_prod.name, null) != null
+ ? { 0 = local.cicd_repositories.project_factory_prod }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "prod-pf-resman-pf-1"
+ display_name = "Terraform CI/CD project factory production service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ var.automation.federated_identity_pool,
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ var.automation.federated_identity_pool,
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-security.tf b/fast/stages-multitenant/1-resman-tenant/cicd-security.tf
new file mode 100644
index 000000000..5cb1581cf
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/cicd-security.tf
@@ -0,0 +1,94 @@
+/**
+ * Copyright 2023 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 CI/CD resources for the security branch.
+
+# source repository
+
+module "branch-security-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.security.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.security }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = [module.branch-security-sa.iam_email]
+ "roles/source.reader" = [module.branch-security-sa-cicd.0.iam_email]
+ }
+ triggers = {
+ fast-02-security = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
+ service_account = module.branch-security-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-security-sa-cicd]
+}
+
+# SA used by CI/CD workflows to impersonate automation SAs
+
+module "branch-security-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.security.name, null) != null
+ ? { 0 = local.cicd_repositories.security }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "prod-resman-sec-1"
+ display_name = "Terraform CI/CD stage 2 security service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/data/org-policies/compute.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/compute.yaml
new file mode 100644
index 000000000..0d27ac426
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/data/org-policies/compute.yaml
@@ -0,0 +1,73 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+compute.disableGuestAttributesAccess:
+ enforce: true
+
+compute.requireOsLogin:
+ enforce: true
+
+compute.restrictLoadBalancerCreationForTypes:
+ allow:
+ values:
+ - in:INTERNAL
+
+compute.skipDefaultNetworkCreation:
+ enforce: true
+
+compute.vmExternalIpAccess:
+ deny:
+ all: true
+
+
+# compute.disableInternetNetworkEndpointGroup:
+# enforce: true
+
+# compute.disableNestedVirtualization:
+# enforce: true
+
+# compute.disableSerialPortAccess:
+# enforce: true
+
+# compute.restrictCloudNATUsage:
+# deny:
+# all: true
+
+# compute.restrictDedicatedInterconnectUsage:
+# deny:
+# all: true
+
+# compute.restrictPartnerInterconnectUsage:
+# deny:
+# all: true
+
+# compute.restrictProtocolForwardingCreationForTypes:
+# deny:
+# all: true
+
+# compute.restrictSharedVpcHostProjects:
+# deny:
+# all: true
+
+# compute.restrictSharedVpcSubnetworks:
+# deny:
+# all: true
+
+# compute.restrictVpcPeering:
+# deny:
+# all: true
+
+# compute.restrictVpnPeerIPs:
+# deny:
+# all: true
+
+# compute.restrictXpnProjectLienRemoval:
+# enforce: true
+
+# compute.setNewProjectDefaultToZonalDNSOnly:
+# enforce: true
+
+# compute.vmCanIpForward:
+# deny:
+# all: true
diff --git a/fast/stages-multitenant/1-resman-tenant/data/org-policies/iam.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/iam.yaml
new file mode 100644
index 000000000..4d83f827f
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/data/org-policies/iam.yaml
@@ -0,0 +1,12 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+iam.automaticIamGrantsForDefaultServiceAccounts:
+ enforce: true
+
+iam.disableServiceAccountKeyCreation:
+ enforce: true
+
+iam.disableServiceAccountKeyUpload:
+ enforce: true
diff --git a/fast/stages-multitenant/1-resman-tenant/data/org-policies/serverless.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/serverless.yaml
new file mode 100644
index 000000000..de62e6c70
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/data/org-policies/serverless.yaml
@@ -0,0 +1,26 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+run.allowedIngress:
+ allow:
+ values:
+ - is:internal
+
+# run.allowedVPCEgress:
+# allow:
+# values:
+# - is:private-ranges-only
+
+# cloudfunctions.allowedIngressSettings:
+# allow:
+# values:
+# - is:ALLOW_INTERNAL_ONLY
+
+# cloudfunctions.allowedVpcConnectorEgressSettings:
+# allow:
+# values:
+# - is:PRIVATE_RANGES_ONLY
+
+# cloudfunctions.requireVPCConnector:
+# enforce: true
diff --git a/fast/stages-multitenant/1-resman-tenant/data/org-policies/sql.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/sql.yaml
new file mode 100644
index 000000000..88b84d9d5
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/data/org-policies/sql.yaml
@@ -0,0 +1,9 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+sql.restrictAuthorizedNetworks:
+ enforce: true
+
+sql.restrictPublicIp:
+ enforce: true
diff --git a/fast/stages-multitenant/1-resman-tenant/data/org-policies/storage.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/storage.yaml
new file mode 100644
index 000000000..6c0a673f3
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/data/org-policies/storage.yaml
@@ -0,0 +1,6 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+storage.uniformBucketLevelAccess:
+ enforce: true
diff --git a/fast/stages/01-resman/diagram.png b/fast/stages-multitenant/1-resman-tenant/diagram.png
similarity index 100%
rename from fast/stages/01-resman/diagram.png
rename to fast/stages-multitenant/1-resman-tenant/diagram.png
diff --git a/fast/stages/01-resman/diagram.svg b/fast/stages-multitenant/1-resman-tenant/diagram.svg
similarity index 100%
rename from fast/stages/01-resman/diagram.svg
rename to fast/stages-multitenant/1-resman-tenant/diagram.svg
diff --git a/fast/stages-multitenant/1-resman-tenant/main.tf b/fast/stages-multitenant/1-resman-tenant/main.tf
new file mode 100644
index 000000000..76c046396
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/main.tf
@@ -0,0 +1,79 @@
+/**
+ * Copyright 2023 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 {
+ automation_resman_sa_iam = [
+ "serviceAccount:${var.automation.service_accounts.resman}"
+ ]
+ automation_sas_iam = {
+ for k, v in var.automation.service_accounts :
+ k => v == null ? null : "serviceAccount:${v}"
+ }
+ branch_optional_sa_lists = {
+ dp-dev = compact([local.automation_sas_iam.dp-dev])
+ dp-prod = compact([local.automation_sas_iam.dp-prod])
+ gke-dev = compact([local.automation_sas_iam.gke-dev])
+ gke-prod = compact([local.automation_sas_iam.gke-prod])
+ pf-dev = compact([local.automation_sas_iam.pf-dev])
+ pf-prod = compact([local.automation_sas_iam.pf-prod])
+ }
+ # derive identity pool names from identity providers for easy reference
+ cicd_identity_pools = {
+ for k, v in local.cicd_identity_providers :
+ k => split("/providers/", v.name)[0]
+ }
+ cicd_identity_providers = coalesce(
+ try(var.automation.federated_identity_providers, null), {}
+ )
+ cicd_repositories = {
+ for k, v in coalesce(var.cicd_repositories, {}) : k => v
+ if(
+ v != null &&
+ (
+ try(v.type, null) == "sourcerepo"
+ ||
+ contains(
+ keys(local.cicd_identity_providers),
+ coalesce(try(v.identity_provider, null), ":")
+ )
+ ) &&
+ fileexists("${path.module}/templates/workflow-${try(v.type, "")}.yaml")
+ )
+ }
+ cicd_workflow_var_files = {
+ stage_2 = [
+ "0-bootstrap-tenant.auto.tfvars.json",
+ ]
+ stage_3 = [
+ "0-bootstrap-tenant.auto.tfvars.json",
+ "2-networking.auto.tfvars.json",
+ "2-security.auto.tfvars.json"
+ ]
+ }
+ custom_roles = coalesce(var.custom_roles, {})
+ gcs_storage_class = (
+ length(split("-", var.locations.gcs)) < 2
+ ? "MULTI_REGIONAL"
+ : "REGIONAL"
+ )
+ groups = {
+ for k, v in var.groups :
+ k => v == null ? null : "${v}@${var.organization.domain}"
+ }
+ groups_iam = {
+ for k, v in local.groups : k => v != null ? "group:${v}" : null
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/outputs-files.tf b/fast/stages-multitenant/1-resman-tenant/outputs-files.tf
new file mode 100644
index 000000000..29d5ed460
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/outputs-files.tf
@@ -0,0 +1,46 @@
+/**
+ * Copyright 2023 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 Output files persistence to local filesystem.
+
+locals {
+ outputs_root = join("/", [
+ try(pathexpand(var.outputs_location), ""),
+ "tenants",
+ var.short_name
+ ])
+}
+
+resource "local_file" "providers" {
+ for_each = var.outputs_location == null ? {} : local.providers
+ file_permission = "0644"
+ filename = "${local.outputs_root}/providers/${each.key}-providers.tf"
+ content = try(each.value, null)
+}
+
+resource "local_file" "tfvars" {
+ count = var.outputs_location == null ? 0 : 1
+ file_permission = "0644"
+ filename = "${local.outputs_root}/tfvars/1-resman.auto.tfvars.json"
+ content = jsonencode(local.tfvars)
+}
+
+resource "local_file" "workflows" {
+ for_each = var.outputs_location == null ? {} : local.cicd_workflows
+ file_permission = "0644"
+ filename = "${local.outputs_root}/workflows/${replace(each.key, "_", "-")}-workflow.yaml"
+ content = try(each.value, null)
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/outputs-gcs.tf b/fast/stages-multitenant/1-resman-tenant/outputs-gcs.tf
new file mode 100644
index 000000000..6b0fc89cb
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/outputs-gcs.tf
@@ -0,0 +1,37 @@
+/**
+ * Copyright 2023 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 Output files persistence to automation GCS bucket.
+
+resource "google_storage_bucket_object" "providers" {
+ for_each = local.providers
+ bucket = var.automation.outputs_bucket
+ name = "providers/${each.key}-providers.tf"
+ content = each.value
+}
+
+resource "google_storage_bucket_object" "tfvars" {
+ bucket = var.automation.outputs_bucket
+ name = "tfvars/1-resman.auto.tfvars.json"
+ content = jsonencode(local.tfvars)
+}
+
+resource "google_storage_bucket_object" "workflows" {
+ for_each = local.cicd_workflows
+ bucket = var.automation.outputs_bucket
+ name = "workflows/${replace(each.key, "_", "-")}-workflow.yaml"
+ content = each.value
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/outputs.tf b/fast/stages-multitenant/1-resman-tenant/outputs.tf
new file mode 100644
index 000000000..592f995ea
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/outputs.tf
@@ -0,0 +1,311 @@
+/**
+ * Copyright 2023 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 {
+ _tpl_providers = "${path.module}/templates/providers.tf.tpl"
+ cicd_workflow_attrs = {
+ data_platform_dev = {
+ service_account = try(module.branch-dp-dev-sa-cicd.0.email, null)
+ tf_providers_file = "3-data-platform-dev-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_3
+ }
+ data_platform_prod = {
+ service_account = try(module.branch-dp-prod-sa-cicd.0.email, null)
+ tf_providers_file = "3-data-platform-prod-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_3
+ }
+ gke_dev = {
+ service_account = try(module.branch-gke-dev-sa-cicd.0.email, null)
+ tf_providers_file = "3-gke-dev-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_3
+ }
+ gke_prod = {
+ service_account = try(module.branch-gke-prod-sa-cicd.0.email, null)
+ tf_providers_file = "3-gke-prod-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_3
+ }
+ networking = {
+ service_account = try(module.branch-network-sa-cicd.0.email, null)
+ tf_providers_file = "2-networking-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_2
+ }
+ project_factory_dev = {
+ service_account = try(module.branch-pf-dev-sa-cicd.0.email, null)
+ tf_providers_file = "3-project-factory-dev-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_3
+ }
+ project_factory_prod = {
+ service_account = try(module.branch-pf-prod-sa-cicd.0.email, null)
+ tf_providers_file = "3-project-factory-prod-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_3
+ }
+ security = {
+ service_account = try(module.branch-security-sa-cicd.0.email, null)
+ tf_providers_file = "2-security-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_2
+ }
+ }
+ cicd_workflows = {
+ for k, v in local.cicd_repositories : k => templatefile(
+ "${path.module}/templates/workflow-${v.type}.yaml",
+ merge(local.cicd_workflow_attrs[k], {
+ identity_provider = try(
+ local.cicd_identity_providers[v.identity_provider].name, null
+ )
+ outputs_bucket = var.automation.outputs_bucket
+ stage_name = k
+ })
+ )
+ }
+ folder_ids = merge(
+ {
+ data-platform-dev = try(module.branch-dp-dev-folder.0.id, null)
+ data-platform-prod = try(module.branch-dp-prod-folder.0.id, null)
+ gke-dev = try(module.branch-gke-dev-folder.0.id, null)
+ gke-prod = try(module.branch-gke-prod-folder.0.id, null)
+ networking = module.branch-network-folder.id
+ networking-dev = module.branch-network-dev-folder.id
+ networking-prod = module.branch-network-prod-folder.id
+ sandbox = try(module.branch-sandbox-folder.0.id, null)
+ security = module.branch-security-folder.id
+ teams = try(module.branch-teams-folder.0.id, null)
+ },
+ {
+ for k, v in module.branch-teams-team-folder :
+ "team-${k}" => v.id
+ },
+ {
+ for k, v in module.branch-teams-team-dev-folder :
+ "team-${k}-dev" => v.id
+ },
+ {
+ for k, v in module.branch-teams-team-prod-folder :
+ "team-${k}-prod" => v.id
+ }
+ )
+ providers = merge(
+ {
+ "2-networking" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-network-gcs.name
+ name = "networking"
+ sa = module.branch-network-sa.email
+ })
+ "2-security" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-security-gcs.name
+ name = "security"
+ sa = module.branch-security-sa.email
+ })
+ },
+ !var.fast_features.data_platform ? {} : {
+ "3-data-platform-dev" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-dp-dev-gcs.0.name
+ name = "dp-dev"
+ sa = module.branch-dp-dev-sa.0.email
+ })
+ "3-data-platform-prod" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-dp-prod-gcs.0.name
+ name = "dp-prod"
+ sa = module.branch-dp-prod-sa.0.email
+ })
+ },
+ !var.fast_features.gke ? {} : {
+ "3-gke-dev" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-gke-dev-gcs.0.name
+ name = "gke-dev"
+ sa = module.branch-gke-dev-sa.0.email
+ })
+ "3-gke-prod" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-gke-prod-gcs.0.name
+ name = "gke-prod"
+ sa = module.branch-gke-prod-sa.0.email
+ })
+ },
+ !var.fast_features.project_factory ? {} : {
+ "3-project-factory-dev" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-pf-dev-gcs.0.name
+ name = "team-dev"
+ sa = var.automation.service_accounts.pf-dev
+ })
+ "3-project-factory-prod" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-pf-prod-gcs.0.name
+ name = "team-prod"
+ sa = var.automation.service_accounts.pf-prod
+ })
+ },
+ !var.fast_features.sandbox ? {} : {
+ "9-sandbox" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-sandbox-gcs.0.name
+ name = "sandbox"
+ sa = var.automation.service_accounts.sandbox
+ })
+ },
+ !var.fast_features.teams ? {} : merge(
+ {
+ "3-teams" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-teams-gcs.0.name
+ name = "teams"
+ sa = module.branch-teams-sa.0.email
+ })
+ },
+ {
+ for k, v in module.branch-teams-team-sa :
+ "3-teams-${k}" => templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-teams-team-gcs[k].name
+ name = "teams"
+ sa = v.email
+ })
+ }
+ )
+ )
+ tfvars = {
+ folder_ids = local.folder_ids
+ }
+}
+
+output "cicd_repositories" {
+ description = "WIF configuration for CI/CD repositories."
+ value = {
+ for k, v in local.cicd_repositories : k => {
+ branch = v.branch
+ name = v.name
+ provider = try(
+ local.cicd_identity_providers[v.identity_provider].name, null
+ )
+ service_account = local.cicd_workflow_attrs[k].service_account
+ } if v != null
+ }
+}
+
+output "dataplatform" {
+ description = "Data for the Data Platform stage."
+ value = !var.fast_features.data_platform ? {} : {
+ dev = {
+ folder = module.branch-dp-dev-folder.0.id
+ gcs_bucket = module.branch-dp-dev-gcs.0.name
+ service_account = module.branch-dp-dev-sa.0.email
+ }
+ prod = {
+ folder = module.branch-dp-prod-folder.0.id
+ gcs_bucket = module.branch-dp-prod-gcs.0.name
+ service_account = module.branch-dp-prod-sa.0.email
+ }
+ }
+}
+
+output "gke_multitenant" {
+ # tfdoc:output:consumers 03-gke-multitenant
+ description = "Data for the GKE multitenant stage."
+ value = (
+ var.fast_features.gke
+ ? {
+ "dev" = {
+ folder = module.branch-gke-dev-folder.0.id
+ gcs_bucket = module.branch-gke-dev-gcs.0.name
+ service_account = module.branch-gke-dev-sa.0.email
+ }
+ "prod" = {
+ folder = module.branch-gke-prod-folder.0.id
+ gcs_bucket = module.branch-gke-prod-gcs.0.name
+ service_account = module.branch-gke-prod-sa.0.email
+ }
+ }
+ : {}
+ )
+}
+
+output "networking" {
+ description = "Data for the networking stage."
+ value = {
+ folder = module.branch-network-folder.id
+ gcs_bucket = module.branch-network-gcs.name
+ service_account = module.branch-network-sa.iam_email
+ }
+}
+
+output "project_factories" {
+ description = "Data for the project factories stage."
+ value = !var.fast_features.project_factory ? {} : {
+ dev = {
+ bucket = module.branch-pf-dev-gcs.0.name
+ sa = var.automation.service_accounts.pf-dev
+ }
+ prod = {
+ bucket = module.branch-pf-prod-gcs.0.name
+ sa = var.automation.service_accounts.pf-prod
+ }
+ }
+}
+
+# ready to use provider configurations for subsequent stages
+output "providers" {
+ # tfdoc:output:consumers 02-networking 02-security 03-dataplatform xx-sandbox xx-teams
+ description = "Terraform provider files for this stage and dependent stages."
+ sensitive = true
+ value = local.providers
+}
+
+output "sandbox" {
+ # tfdoc:output:consumers xx-sandbox
+ description = "Data for the sandbox stage."
+ value = (
+ var.fast_features.sandbox
+ ? {
+ folder = module.branch-sandbox-folder.0.id
+ gcs_bucket = module.branch-sandbox-gcs.0.name
+ service_account = var.automation.service_accounts.sandbox
+ }
+ : null
+ )
+}
+
+output "security" {
+ # tfdoc:output:consumers 02-security
+ description = "Data for the networking stage."
+ value = {
+ folder = module.branch-security-folder.id
+ gcs_bucket = module.branch-security-gcs.name
+ service_account = module.branch-security-sa.iam_email
+ }
+}
+
+output "teams" {
+ description = "Data for the teams stage."
+ value = {
+ for k, v in module.branch-teams-team-folder : k => {
+ folder = v.id
+ gcs_bucket = module.branch-teams-team-gcs[k].name
+ service_account = module.branch-teams-team-sa[k].email
+ }
+ }
+}
+
+# ready to use variable values for subsequent stages
+output "tfvars" {
+ description = "Terraform variable files for the following stages."
+ sensitive = true
+ value = local.tfvars
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/root_node.tf b/fast/stages-multitenant/1-resman-tenant/root_node.tf
new file mode 100644
index 000000000..5b83d2dd2
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/root_node.tf
@@ -0,0 +1,41 @@
+/**
+ * Copyright 2023 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 Tenant root folder configuration.
+
+module "root-folder" {
+ source = "../../../modules/folder"
+ id = var.root_node
+ folder_create = var.test_skip_data_sources
+ # start test attributes
+ parent = (
+ var.test_skip_data_sources ? "organizations/${var.organization.id}" : null
+ )
+ name = var.test_skip_data_sources ? "Test" : null
+ # end test attributes
+ iam_additive = {
+ "roles/accesscontextmanager.policyAdmin" = [
+ local.automation_sas_iam.security
+ ]
+ "roles/compute.orgFirewallPolicyAdmin" = [
+ local.automation_sas_iam.networking
+ ]
+ "roles/compute.xpnAdmin" = [
+ local.automation_sas_iam.networking
+ ]
+ }
+ org_policies_data_path = var.organization_policy_data_path
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/templates/providers.tf.tpl b/fast/stages-multitenant/1-resman-tenant/templates/providers.tf.tpl
new file mode 100644
index 000000000..993c78ca4
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/templates/providers.tf.tpl
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2023 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.
+ */
+
+terraform {
+ backend "gcs" {
+ bucket = "${bucket}"
+ impersonate_service_account = "${sa}"
+ %{~ if backend_extra != null ~}
+ ${indent(4, backend_extra)}
+ %{~ endif ~}
+ }
+}
+provider "google" {
+ impersonate_service_account = "${sa}"
+}
+provider "google-beta" {
+ impersonate_service_account = "${sa}"
+}
+
+# end provider.tf for ${name}
diff --git a/fast/stages-multitenant/1-resman-tenant/templates/workflow-github.yaml b/fast/stages-multitenant/1-resman-tenant/templates/workflow-github.yaml
new file mode 100644
index 000000000..e96699092
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/templates/workflow-github.yaml
@@ -0,0 +1,198 @@
+# 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.
+
+name: "FAST ${stage_name} stage"
+
+on:
+ pull_request:
+ branches:
+ - main
+ types:
+ - closed
+ - opened
+ - synchronize
+
+env:
+ FAST_OUTPUTS_BUCKET: ${outputs_bucket}
+ FAST_SERVICE_ACCOUNT: ${service_account}
+ FAST_WIF_PROVIDER: ${identity_provider}
+ SSH_AUTH_SOCK: /tmp/ssh_agent.sock
+ TF_PROVIDERS_FILE: ${tf_providers_file}
+ TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)}
+ TF_VERSION: 1.3.2
+
+jobs:
+ fast-pr:
+ permissions:
+ contents: read
+ id-token: write
+ issues: write
+ pull-requests: write
+ runs-on: ubuntu-latest
+ steps:
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@v3
+
+ # set up SSH key authentication to the modules repository
+ - id: ssh-config
+ name: Configure SSH authentication
+ run: |
+ ssh-agent -a "$SSH_AUTH_SOCK" > /dev/null
+ ssh-add - <<< "$${{ secrets.CICD_MODULES_KEY }}"
+
+ # set up authentication via Workload identity Federation
+ - id: gcp-auth
+ name: Authenticate to Google Cloud
+ uses: google-github-actions/auth@v0
+ with:
+ workload_identity_provider: $${{ env.FAST_WIF_PROVIDER }}
+ service_account: $${{ env.FAST_SERVICE_ACCOUNT }}
+ access_token_lifetime: 3600s
+
+ - id: gcp-sdk
+ name: Set up Cloud SDK
+ uses: google-github-actions/setup-gcloud@v0
+ with:
+ install_components: alpha
+
+ # copy provider and tfvars files
+ - id: tf-config
+ name: Copy Terraform output files
+ run: |
+ gcloud alpha storage cp -r \
+ "gs://$${{env.FAST_OUTPUTS_BUCKET}}/providers/$${{env.TF_PROVIDERS_FILE}}" ./
+ gcloud alpha storage cp -r \
+ "gs://$${{env.FAST_OUTPUTS_BUCKET}}/tfvars" ./
+ for f in $${{env.TF_VAR_FILES}}; do
+ ln -s "tfvars/$f" ./
+ done
+
+ - id: tf-setup
+ name: Set up Terraform
+ uses: hashicorp/setup-terraform@v2.0.3
+ with:
+ terraform_version: $${{ env.TF_VERSION }}
+
+ # run Terraform init/validate/plan
+ - id: tf-init
+ name: Terraform init
+ continue-on-error: true
+ run: |
+ terraform init -no-color
+
+ - id: tf-validate
+ name: Terraform validate
+ continue-on-error: true
+ run: terraform validate -no-color
+
+ - id: tf-plan
+ name: Terraform plan
+ continue-on-error: true
+ run: |
+ terraform plan -input=false -out ../plan.out -no-color
+
+ - id: tf-apply
+ if: github.event.pull_request.merged == true && success()
+ name: Terraform apply
+ continue-on-error: true
+ run: |
+ terraform apply -input=false -auto-approve -no-color ../plan.out
+
+ - id: pr-comment
+ name: Post comment to Pull Request
+ continue-on-error: true
+ uses: actions/github-script@v6
+ if: github.event_name == 'pull_request'
+ env:
+ PLAN: $${{ steps.tf-plan.outputs.stdout }}\n$${{ steps.tf-plan.outputs.stderr }}
+ with:
+ script: |
+ const output = `### Terraform Initialization \`$${{ steps.tf-init.outcome }}\`
+
+ ### Terraform Validation \`$${{ steps.tf-validate.outcome }}\`
+
+
+
+
gcs · iam-service-account · project | |
-| [billing.tf](./billing.tf) | Billing export project and dataset. | bigquery-dataset · organization · project | google_billing_account_iam_member · google_organization_iam_binding |
+| [billing.tf](./billing.tf) | Billing export project and dataset. | bigquery-dataset · project | google_billing_account_iam_member |
| [cicd.tf](./cicd.tf) | Workload Identity Federation configurations for CI/CD. | iam-service-account · source-repository | |
| [identity-providers.tf](./identity-providers.tf) | Workload Identity Federation provider definitions. | | google_iam_workload_identity_pool · google_iam_workload_identity_pool_provider |
| [log-export.tf](./log-export.tf) | Audit log project and sink. | bigquery-dataset · gcs · logging-bucket · project · pubsub | |
@@ -464,35 +481,35 @@ The remaining configuration is manual, as it regards the repositories themselves
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [billing_account](variables.tf#L17) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | |
-| [organization](variables.tf#L202) | Organization details. | object({…}) | ✓ | | |
-| [prefix](variables.tf#L217) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | |
-| [bootstrap_user](variables.tf#L25) | Email of the nominal user running this stage for the first time. | string | | null | |
-| [cicd_repositories](variables.tf#L31) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | |
-| [custom_role_names](variables.tf#L83) | Names of custom roles defined at the org level. | object({…}) | | {…} | |
-| [fast_features](variables.tf#L95) | Selective control for top-level FAST features. | object({…}) | | {…} | |
-| [federated_identity_providers](variables.tf#L114) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | |
-| [groups](variables.tf#L128) | Group names to grant organization-level permissions. | map(string) | | {…} | |
-| [iam](variables.tf#L146) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | |
-| [iam_additive](variables.tf#L152) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | map(list(string)) | | {} | |
-| [locations](variables.tf#L158) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | |
-| [log_sinks](variables.tf#L177) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | |
-| [outputs_location](variables.tf#L211) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | |
-| [project_parent_ids](variables.tf#L227) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {…} | |
+| [billing_account](variables.tf#L17) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | |
+| [organization](variables.tf#L196) | Organization details. | object({…}) | ✓ | | |
+| [prefix](variables.tf#L211) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | |
+| [bootstrap_user](variables.tf#L29) | Email of the nominal user running this stage for the first time. | string | | null | |
+| [cicd_repositories](variables.tf#L35) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | |
+| [custom_role_names](variables.tf#L81) | Names of custom roles defined at the org level. | object({…}) | | {…} | |
+| [fast_features](variables.tf#L95) | Selective control for top-level FAST features. | object({…}) | | {} | |
+| [federated_identity_providers](variables.tf#L108) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…})) | | {} | |
+| [groups](variables.tf#L122) | Group names to grant organization-level permissions. | map(string) | | {…} | |
+| [iam](variables.tf#L140) | Organization-level custom IAM settings in role => [principal] format. | map(list(string)) | | {} | |
+| [iam_additive](variables.tf#L146) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | map(list(string)) | | {} | |
+| [locations](variables.tf#L152) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | |
+| [log_sinks](variables.tf#L171) | Org-level log sinks, in name => {type, filter} format. | map(object({…})) | | {…} | |
+| [outputs_location](variables.tf#L205) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | |
+| [project_parent_ids](variables.tf#L221) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…}) | | {…} | |
## Outputs
| name | description | sensitive | consumers |
|---|---|:---:|---|
-| [automation](outputs.tf#L89) | Automation resources. | | |
-| [billing_dataset](outputs.tf#L94) | BigQuery dataset prepared for billing export. | | |
-| [cicd_repositories](outputs.tf#L99) | CI/CD repository configurations. | | |
-| [custom_roles](outputs.tf#L111) | Organization-level custom roles. | | |
-| [federated_identity](outputs.tf#L116) | Workload Identity Federation pool and providers. | | |
-| [outputs_bucket](outputs.tf#L126) | GCS bucket where generated output files are stored. | | |
-| [project_ids](outputs.tf#L131) | Projects created by this stage. | | |
-| [providers](outputs.tf#L141) | Terraform provider files for this stage and dependent stages. | ✓ | stage-01 |
-| [service_accounts](outputs.tf#L148) | Automation service accounts created by this stage. | | |
-| [tfvars](outputs.tf#L158) | Terraform variable files for the following stages. | ✓ | |
+| [automation](outputs.tf#L86) | Automation resources. | | |
+| [billing_dataset](outputs.tf#L91) | BigQuery dataset prepared for billing export. | | |
+| [cicd_repositories](outputs.tf#L96) | CI/CD repository configurations. | | |
+| [custom_roles](outputs.tf#L108) | Organization-level custom roles. | | |
+| [federated_identity](outputs.tf#L113) | Workload Identity Federation pool and providers. | | |
+| [outputs_bucket](outputs.tf#L123) | GCS bucket where generated output files are stored. | | |
+| [project_ids](outputs.tf#L128) | Projects created by this stage. | | |
+| [providers](outputs.tf#L138) | Terraform provider files for this stage and dependent stages. | ✓ | stage-01 |
+| [service_accounts](outputs.tf#L145) | Automation service accounts created by this stage. | | |
+| [tfvars](outputs.tf#L154) | Terraform variable files for the following stages. | ✓ | |
diff --git a/fast/stages/00-bootstrap/automation.tf b/fast/stages/0-bootstrap/automation.tf
similarity index 82%
rename from fast/stages/00-bootstrap/automation.tf
rename to fast/stages/0-bootstrap/automation.tf
index 1475c811c..90c14a81d 100644
--- a/fast/stages/00-bootstrap/automation.tf
+++ b/fast/stages/0-bootstrap/automation.tf
@@ -127,39 +127,6 @@ module "automation-tf-bootstrap-sa" {
}
}
-# cicd stage's bucket and service account
-
-module "automation-tf-cicd-gcs" {
- source = "../../../modules/gcs"
- project_id = module.automation-project.project_id
- name = "iac-core-cicd-0"
- prefix = local.prefix
- location = var.locations.gcs
- storage_class = local.gcs_storage_class
- versioning = true
- iam = {
- "roles/storage.objectAdmin" = [module.automation-tf-cicd-provisioning-sa.iam_email]
- }
- depends_on = [module.organization]
-}
-
-module "automation-tf-cicd-provisioning-sa" {
- source = "../../../modules/iam-service-account"
- project_id = module.automation-project.project_id
- name = "cicd-0"
- display_name = "Terraform stage 1 CICD service account."
- prefix = local.prefix
- # allow SA used by CI/CD workflow to impersonate this SA
- iam = {
- "roles/iam.serviceAccountTokenCreator" = compact([
- try(module.automation-tf-cicd-sa["cicd"].iam_email, null)
- ])
- }
- iam_storage_roles = {
- (module.automation-tf-output-gcs.name) = ["roles/storage.admin"]
- }
-}
-
# resource hierarchy stage's bucket and service account
module "automation-tf-resman-gcs" {
@@ -183,7 +150,8 @@ module "automation-tf-resman-sa" {
display_name = "Terraform stage 1 resman service account."
prefix = local.prefix
# allow SA used by CI/CD workflow to impersonate this SA
- iam = {
+ # we use additive IAM to allow tenant CI/CD SAs to impersonate it
+ iam_additive = {
"roles/iam.serviceAccountTokenCreator" = compact([
try(module.automation-tf-cicd-sa["resman"].iam_email, null)
])
diff --git a/fast/stages/00-bootstrap/billing.tf b/fast/stages/0-bootstrap/billing.tf
similarity index 60%
rename from fast/stages/00-bootstrap/billing.tf
rename to fast/stages/0-bootstrap/billing.tf
index df10e8f08..aee033bd8 100644
--- a/fast/stages/00-bootstrap/billing.tf
+++ b/fast/stages/0-bootstrap/billing.tf
@@ -30,7 +30,7 @@ locals {
module "billing-export-project" {
source = "../../../modules/project"
- count = local.billing_org ? 1 : 0
+ count = var.billing_account.is_org_level ? 1 : 0
billing_account = var.billing_account.id
name = "billing-exp-0"
parent = coalesce(
@@ -52,56 +52,18 @@ module "billing-export-project" {
module "billing-export-dataset" {
source = "../../../modules/bigquery-dataset"
- count = local.billing_org ? 1 : 0
+ count = var.billing_account.is_org_level ? 1 : 0
project_id = module.billing-export-project.0.project_id
id = "billing_export"
friendly_name = "Billing export."
location = var.locations.bq
}
-# billing account in a different org
-
-module "billing-organization-ext" {
- source = "../../../modules/organization"
- count = local.billing_org_ext ? 1 : 0
- organization_id = "organizations/${var.billing_account.organization_id}"
- iam_additive = {
- "roles/billing.admin" = local.billing_ext_admins
- }
-}
-
-
-resource "google_organization_iam_binding" "billing_org_ext_admin_delegated" {
- # refer to organization.tf for the explanation of how this binding works
- count = local.billing_org_ext ? 1 : 0
- org_id = var.billing_account.organization_id
- # if the billing org does not have our custom role, user the predefined one
- # role = "roles/resourcemanager.organizationAdmin"
- role = join("", [
- "organizations/${var.billing_account.organization_id}/",
- "roles/${var.custom_role_names.organization_iam_admin}"
- ])
- members = [module.automation-tf-resman-sa.iam_email]
- condition {
- title = "automation_sa_delegated_grants"
- description = "Automation service account delegated grants."
- expression = format(
- "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])",
- join(",", formatlist("'%s'", [
- "roles/billing.costsManager",
- "roles/billing.user",
- ]
- ))
- )
- }
- depends_on = [module.billing-organization-ext]
-}
-
# standalone billing account
resource "google_billing_account_iam_member" "billing_ext_admin" {
for_each = toset(
- local.billing_ext ? local.billing_ext_admins : []
+ !var.billing_account.is_org_level ? local.billing_ext_admins : []
)
billing_account_id = var.billing_account.id
role = "roles/billing.admin"
@@ -110,7 +72,7 @@ resource "google_billing_account_iam_member" "billing_ext_admin" {
resource "google_billing_account_iam_member" "billing_ext_cost_manager" {
for_each = toset(
- local.billing_ext ? local.billing_ext_admins : []
+ !var.billing_account.is_org_level ? local.billing_ext_admins : []
)
billing_account_id = var.billing_account.id
role = "roles/billing.costsManager"
diff --git a/fast/stages/00-bootstrap/cicd.tf b/fast/stages/0-bootstrap/cicd.tf
similarity index 86%
rename from fast/stages/00-bootstrap/cicd.tf
rename to fast/stages/0-bootstrap/cicd.tf
index 7cdae41c9..2b2a3df95 100644
--- a/fast/stages/00-bootstrap/cicd.tf
+++ b/fast/stages/0-bootstrap/cicd.tf
@@ -17,6 +17,16 @@
# tfdoc:file:description Workload Identity Federation configurations for CI/CD.
locals {
+ cicd_providers = {
+ for k, v in google_iam_workload_identity_pool_provider.default :
+ k => {
+ issuer = local.identity_providers[k].issuer
+ issuer_uri = local.identity_providers[k].issuer_uri
+ name = v.name
+ principal_tpl = local.identity_providers[k].principal_tpl
+ principalset_tpl = local.identity_providers[k].principalset_tpl
+ }
+ }
cicd_repositories = {
for k, v in coalesce(var.cicd_repositories, {}) : k => v
if(
@@ -32,18 +42,13 @@ locals {
)
}
cicd_workflow_providers = {
- bootstrap = "00-bootstrap-providers.tf"
- cicd = "00-cicd-providers.tf"
- resman = "01-resman-providers.tf"
+ bootstrap = "0-bootstrap-providers.tf"
+ resman = "1-resman-providers.tf"
}
cicd_workflow_var_files = {
bootstrap = []
- cicd = [
- "00-bootstrap.auto.tfvars.json",
- "globals.auto.tfvars.json"
- ]
resman = [
- "00-bootstrap.auto.tfvars.json",
+ "0-bootstrap.auto.tfvars.json",
"globals.auto.tfvars.json"
]
}
@@ -69,7 +74,7 @@ module "automation-tf-cicd-repo" {
]
}
triggers = {
- "fast-00-${each.key}" = {
+ "fast-0-${each.key}" = {
filename = ".cloudbuild/workflow.yaml"
included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
service_account = module.automation-tf-cicd-sa[each.key].id
diff --git a/fast/stages/00-bootstrap/diagram.png b/fast/stages/0-bootstrap/diagram.png
similarity index 100%
rename from fast/stages/00-bootstrap/diagram.png
rename to fast/stages/0-bootstrap/diagram.png
diff --git a/fast/stages/00-bootstrap/diagram.svg b/fast/stages/0-bootstrap/diagram.svg
similarity index 100%
rename from fast/stages/00-bootstrap/diagram.svg
rename to fast/stages/0-bootstrap/diagram.svg
diff --git a/fast/stages/00-bootstrap/groups.gif b/fast/stages/0-bootstrap/groups.gif
similarity index 100%
rename from fast/stages/00-bootstrap/groups.gif
rename to fast/stages/0-bootstrap/groups.gif
diff --git a/fast/stages/00-bootstrap/identity-providers.tf b/fast/stages/0-bootstrap/identity-providers.tf
similarity index 100%
rename from fast/stages/00-bootstrap/identity-providers.tf
rename to fast/stages/0-bootstrap/identity-providers.tf
diff --git a/fast/stages/00-bootstrap/log-export.tf b/fast/stages/0-bootstrap/log-export.tf
similarity index 82%
rename from fast/stages/00-bootstrap/log-export.tf
rename to fast/stages/0-bootstrap/log-export.tf
index 1c9f5a87a..b9b5da42f 100644
--- a/fast/stages/00-bootstrap/log-export.tf
+++ b/fast/stages/0-bootstrap/log-export.tf
@@ -17,6 +17,16 @@
# tfdoc:file:description Audit log project and sink.
locals {
+ log_sink_destinations = merge(
+ # use the same dataset for all sinks with `bigquery` as destination
+ { for k, v in var.log_sinks : k => module.log-export-dataset.0 if v.type == "bigquery" },
+ # use the same gcs bucket for all sinks with `storage` as destination
+ { for k, v in var.log_sinks : k => module.log-export-gcs.0 if v.type == "storage" },
+ # use separate pubsub topics and logging buckets for sinks with
+ # destination `pubsub` and `logging`
+ module.log-export-pubsub,
+ module.log-export-logbucket
+ )
log_types = toset([for k, v in var.log_sinks : v.type])
}
diff --git a/fast/stages/00-bootstrap/main.tf b/fast/stages/0-bootstrap/main.tf
similarity index 78%
rename from fast/stages/00-bootstrap/main.tf
rename to fast/stages/0-bootstrap/main.tf
index 839019f3c..dba2ed089 100644
--- a/fast/stages/00-bootstrap/main.tf
+++ b/fast/stages/0-bootstrap/main.tf
@@ -28,10 +28,6 @@ locals {
for k, v in local.groups :
k => "group:${v}"
}
- # convenience flags that express where billing account resides
- billing_ext = var.billing_account.organization_id == null
- billing_org = var.billing_account.organization_id == var.organization.id
- billing_org_ext = !local.billing_ext && !local.billing_org
# naming: environment used in most resource names
prefix = join("-", compact([var.prefix, "prod"]))
}
diff --git a/fast/stages/00-bootstrap/organization.tf b/fast/stages/0-bootstrap/organization.tf
similarity index 89%
rename from fast/stages/00-bootstrap/organization.tf
rename to fast/stages/0-bootstrap/organization.tf
index 0700d564e..154b37fe5 100644
--- a/fast/stages/00-bootstrap/organization.tf
+++ b/fast/stages/0-bootstrap/organization.tf
@@ -23,9 +23,13 @@ locals {
"roles/browser" = [
"domain:${var.organization.domain}"
]
- "roles/logging.admin" = [
- module.automation-tf-bootstrap-sa.iam_email
- ]
+ "roles/logging.admin" = concat(
+ [
+ module.automation-tf-bootstrap-sa.iam_email,
+ module.automation-tf-resman-sa.iam_email
+ ],
+ local._iam_bootstrap_user
+ )
"roles/owner" = local._iam_bootstrap_user
"roles/resourcemanager.folderAdmin" = [
module.automation-tf-resman-sa.iam_email
@@ -34,12 +38,11 @@ locals {
[module.automation-tf-bootstrap-sa.iam_email],
local._iam_bootstrap_user
)
- # the following is useful if roles/browser is not desirable
- # "roles/resourcemanager.organizationViewer" = [
- # "domain:${var.organization.domain}"
- # ]
"roles/resourcemanager.projectCreator" = concat(
- [module.automation-tf-bootstrap-sa.iam_email],
+ [
+ module.automation-tf-bootstrap-sa.iam_email,
+ module.automation-tf-resman-sa.iam_email
+ ],
local._iam_bootstrap_user
)
"roles/resourcemanager.projectMover" = [
@@ -77,8 +80,12 @@ locals {
local.groups_iam.gcp-security-admins,
module.automation-tf-resman-sa.iam_email
]
+ # the following is useful if roles/browser is not desirable
+ # "roles/resourcemanager.organizationViewer" = [
+ # "domain:${var.organization.domain}"
+ # ]
},
- local.billing_org ? {
+ var.billing_account.is_org_level ? {
"roles/billing.admin" = [
local.groups_iam.gcp-billing-admins,
local.groups_iam.gcp-organization-admins,
@@ -114,16 +121,6 @@ locals {
iam_roles_additive = distinct(concat(
keys(local._iam_additive), keys(var.iam_additive)
))
- log_sink_destinations = merge(
- # use the same dataset for all sinks with `bigquery` as destination
- { for k, v in var.log_sinks : k => module.log-export-dataset.0 if v.type == "bigquery" },
- # use the same gcs bucket for all sinks with `storage` as destination
- { for k, v in var.log_sinks : k => module.log-export-gcs.0 if v.type == "storage" },
- # use separate pubsub topics and logging buckets for sinks with
- # destination `pubsub` and `logging`
- module.log-export-pubsub,
- module.log-export-logbucket
- )
}
module "organization" {
@@ -189,6 +186,9 @@ module "organization" {
"dns.networks.bindPrivateDNSZone",
"resourcemanager.projects.get",
]
+ (var.custom_role_names.tenant_network_admin) = [
+ "compute.globalOperations.get",
+ ]
}
logging_sinks = {
for name, attrs in var.log_sinks : name => {
@@ -219,8 +219,10 @@ resource "google_organization_iam_binding" "org_admin_delegated" {
"roles/compute.orgFirewallPolicyAdmin",
"roles/compute.xpnAdmin",
"roles/orgpolicy.policyAdmin",
+ "roles/resourcemanager.organizationViewer",
+ module.organization.custom_role_id[var.custom_role_names.tenant_network_admin]
],
- local.billing_org ? [
+ var.billing_account.is_org_level ? [
"roles/billing.admin",
"roles/billing.costsManager",
"roles/billing.user",
diff --git a/fast/stages/00-bootstrap/outputs-files.tf b/fast/stages/0-bootstrap/outputs-files.tf
similarity index 97%
rename from fast/stages/00-bootstrap/outputs-files.tf
rename to fast/stages/0-bootstrap/outputs-files.tf
index ded88cd56..90e195b53 100644
--- a/fast/stages/00-bootstrap/outputs-files.tf
+++ b/fast/stages/0-bootstrap/outputs-files.tf
@@ -26,7 +26,7 @@ resource "local_file" "providers" {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/00-bootstrap.auto.tfvars.json"
+ filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/0-bootstrap.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/00-bootstrap/outputs-gcs.tf b/fast/stages/0-bootstrap/outputs-gcs.tf
similarity index 96%
rename from fast/stages/00-bootstrap/outputs-gcs.tf
rename to fast/stages/0-bootstrap/outputs-gcs.tf
index 2c281d4cc..0aded986a 100644
--- a/fast/stages/00-bootstrap/outputs-gcs.tf
+++ b/fast/stages/0-bootstrap/outputs-gcs.tf
@@ -26,7 +26,7 @@ resource "google_storage_bucket_object" "providers" {
resource "google_storage_bucket_object" "tfvars" {
bucket = module.automation-tf-output-gcs.name
- name = "tfvars/00-bootstrap.auto.tfvars.json"
+ name = "tfvars/0-bootstrap.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/00-bootstrap/outputs.tf b/fast/stages/0-bootstrap/outputs.tf
similarity index 77%
rename from fast/stages/00-bootstrap/outputs.tf
rename to fast/stages/0-bootstrap/outputs.tf
index 73dd64f4e..364abd684 100644
--- a/fast/stages/00-bootstrap/outputs.tf
+++ b/fast/stages/0-bootstrap/outputs.tf
@@ -21,7 +21,7 @@ locals {
for k, v in local.cicd_repositories : k => templatefile(
"${path.module}/templates/workflow-${v.type}.yaml", {
identity_provider = try(
- local.wif_providers[v["identity_provider"]].name, ""
+ local.cicd_providers[v["identity_provider"]].name, ""
)
outputs_bucket = module.automation-tf-output-gcs.name
service_account = try(
@@ -38,19 +38,26 @@ locals {
k => try(module.organization.custom_role_id[v], null)
}
providers = {
- "00-bootstrap" = templatefile(local._tpl_providers, {
- bucket = module.automation-tf-bootstrap-gcs.name
- name = "bootstrap"
- sa = module.automation-tf-bootstrap-sa.email
+ "0-bootstrap" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.automation-tf-bootstrap-gcs.name
+ name = "bootstrap"
+ sa = module.automation-tf-bootstrap-sa.email
})
- "00-cicd" = templatefile(local._tpl_providers, {
- bucket = module.automation-tf-cicd-gcs.name
- name = "cicd"
- sa = module.automation-tf-cicd-provisioning-sa.email
+ "1-resman" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.automation-tf-resman-gcs.name
+ name = "resman"
+ sa = module.automation-tf-resman-sa.email
})
- "01-resman" = templatefile(local._tpl_providers, {
+ "0-bootstrap-tenant" = templatefile(local._tpl_providers, {
+ backend_extra = join("\n", [
+ "# remove the newline between quotes and set the tenant name as prefix",
+ "prefix = \"",
+ "\""
+ ])
bucket = module.automation-tf-resman-gcs.name
- name = "resman"
+ name = "bootstrap-tenant"
sa = module.automation-tf-resman-sa.email
})
}
@@ -59,7 +66,7 @@ locals {
federated_identity_pool = try(
google_iam_workload_identity_pool.default.0.name, null
)
- federated_identity_providers = local.wif_providers
+ federated_identity_providers = local.cicd_providers
outputs_bucket = module.automation-tf-output-gcs.name
project_id = module.automation-project.project_id
project_number = module.automation-project.number
@@ -74,16 +81,6 @@ locals {
organization = var.organization
prefix = var.prefix
}
- wif_providers = {
- for k, v in google_iam_workload_identity_pool_provider.default :
- k => {
- issuer = local.identity_providers[k].issuer
- issuer_uri = local.identity_providers[k].issuer_uri
- name = v.name
- principal_tpl = local.identity_providers[k].principal_tpl
- principalset_tpl = local.identity_providers[k].principalset_tpl
- }
- }
}
output "automation" {
@@ -102,7 +99,7 @@ output "cicd_repositories" {
for k, v in local.cicd_repositories : k => {
branch = v.branch
name = v.name
- provider = try(local.wif_providers[v.identity_provider].name, null)
+ provider = try(local.cicd_providers[v.identity_provider].name, null)
service_account = try(module.automation-tf-cicd-sa[k].email, null)
}
}
@@ -119,7 +116,7 @@ output "federated_identity" {
pool = try(
google_iam_workload_identity_pool.default.0.name, null
)
- providers = local.wif_providers
+ providers = local.cicd_providers
}
}
@@ -149,7 +146,6 @@ output "service_accounts" {
description = "Automation service accounts created by this stage."
value = {
bootstrap = module.automation-tf-bootstrap-sa.email
- cicd = module.automation-tf-cicd-provisioning-sa.email
resman = module.automation-tf-resman-sa.email
}
}
diff --git a/tests/modules/gke_cluster/fixture/variables.tf b/fast/stages/0-bootstrap/templates/providers.tf.tpl
similarity index 62%
rename from tests/modules/gke_cluster/fixture/variables.tf
rename to fast/stages/0-bootstrap/templates/providers.tf.tpl
index 97fc6a635..d1c224c5c 100644
--- a/tests/modules/gke_cluster/fixture/variables.tf
+++ b/fast/stages/0-bootstrap/templates/providers.tf.tpl
@@ -14,24 +14,20 @@
* limitations under the License.
*/
-variable "enable_addons" {
- type = any
- default = {
- horizontal_pod_autoscaling = true
- http_load_balancing = true
+terraform {
+ backend "gcs" {
+ bucket = "${bucket}"
+ impersonate_service_account = "${sa}"
+ %{~ if backend_extra != null ~}
+ ${indent(4, backend_extra)}
+ %{~ endif ~}
}
}
+provider "google" {
+ impersonate_service_account = "${sa}"
+}
+provider "google-beta" {
+ impersonate_service_account = "${sa}"
+}
-variable "enable_features" {
- type = any
- default = {
- workload_identity = true
- }
-}
-
-variable "monitoring_config" {
- type = any
- default = {
- managed_prometheus = true
- }
-}
+# end provider.tf for ${name}
diff --git a/fast/stages/0-bootstrap/templates/workflow-github.yaml b/fast/stages/0-bootstrap/templates/workflow-github.yaml
new file mode 100644
index 000000000..87b5ae1a0
--- /dev/null
+++ b/fast/stages/0-bootstrap/templates/workflow-github.yaml
@@ -0,0 +1,202 @@
+# 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.
+
+name: "FAST ${stage_name} stage"
+
+on:
+ pull_request:
+ branches:
+ - main
+ types:
+ - closed
+ - opened
+ - synchronize
+
+env:
+ FAST_OUTPUTS_BUCKET: ${outputs_bucket}
+ FAST_SERVICE_ACCOUNT: ${service_account}
+ FAST_WIF_PROVIDER: ${identity_provider}
+ SSH_AUTH_SOCK: /tmp/ssh_agent.sock
+ TF_PROVIDERS_FILE: ${tf_providers_file}
+ %{~ if tf_var_files != [] ~}
+ TF_VAR_FILES: ${join("\n ", tf_var_files)}
+ %{~ endif ~}
+ TF_VERSION: 1.3.2
+
+jobs:
+ fast-pr:
+ permissions:
+ contents: read
+ id-token: write
+ issues: write
+ pull-requests: write
+ runs-on: ubuntu-latest
+ steps:
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@v3
+
+ # set up SSH key authentication to the modules repository
+ - id: ssh-config
+ name: Configure SSH authentication
+ run: |
+ ssh-agent -a "$SSH_AUTH_SOCK" > /dev/null
+ ssh-add - <<< "$${{ secrets.CICD_MODULES_KEY }}"
+
+ # set up authentication via Workload identity Federation
+ - id: gcp-auth
+ name: Authenticate to Google Cloud
+ uses: google-github-actions/auth@v0
+ with:
+ workload_identity_provider: $${{ env.FAST_WIF_PROVIDER }}
+ service_account: $${{ env.FAST_SERVICE_ACCOUNT }}
+ access_token_lifetime: 3600s
+
+ - id: gcp-sdk
+ name: Set up Cloud SDK
+ uses: google-github-actions/setup-gcloud@v0
+ with:
+ install_components: alpha
+
+ # copy provider and tfvars files
+ - id: tf-config
+ name: Copy Terraform output files
+ run: |
+ gcloud alpha storage cp -r \
+ "gs://$${{env.FAST_OUTPUTS_BUCKET}}/providers/$${{env.TF_PROVIDERS_FILE}}" ./
+ %{~ if tf_var_files != [] ~}
+ gcloud alpha storage cp -r \
+ "gs://$${{env.FAST_OUTPUTS_BUCKET}}/tfvars" ./
+ for f in $${{env.TF_VAR_FILES}}; do
+ ln -s "tfvars/$f" ./
+ done
+ %{~ endif ~}
+
+ - id: tf-setup
+ name: Set up Terraform
+ uses: hashicorp/setup-terraform@v2.0.3
+ with:
+ terraform_version: $${{ env.TF_VERSION }}
+
+ # run Terraform init/validate/plan
+ - id: tf-init
+ name: Terraform init
+ continue-on-error: true
+ run: |
+ terraform init -no-color
+
+ - id: tf-validate
+ continue-on-error: true
+ name: Terraform validate
+ run: terraform validate -no-color
+
+ - id: tf-plan
+ name: Terraform plan
+ continue-on-error: true
+ run: |
+ terraform plan -input=false -out ../plan.out -no-color
+
+ - id: tf-apply
+ if: github.event.pull_request.merged == true && success()
+ name: Terraform apply
+ continue-on-error: true
+ run: |
+ terraform apply -input=false -auto-approve -no-color ../plan.out
+
+ - id: pr-comment
+ name: Post comment to Pull Request
+ continue-on-error: true
+ uses: actions/github-script@v6
+ if: github.event_name == 'pull_request'
+ env:
+ PLAN: $${{ steps.tf-plan.outputs.stdout }}\n$${{ steps.tf-plan.outputs.stderr }}
+ with:
+ script: |
+ const output = `### Terraform Initialization \`$${{ steps.tf-init.outcome }}\`
+
+ ### Terraform Validation \`$${{ steps.tf-validate.outcome }}\`
+
+
-
-
-
-
data-platform-foundations | |
-| [outputs.tf](./outputs.tf) | Output variables. | | google_storage_bucket_object · local_file |
-| [variables.tf](./variables.tf) | Terraform Variables. | | |
-
-## Variables
-
-| name | description | type | required | default | producer |
-|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap |
-| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-globals |
-| [folder_ids](variables.tf#L65) | Folder to be used for the networking resources in folders/nnnn format. | object({…}) | ✓ | | 01-resman |
-| [host_project_ids](variables.tf#L83) | Shared VPC project ids. | object({…}) | ✓ | | 02-networking |
-| [organization](variables.tf#L115) | Organization details. | object({…}) | ✓ | | 00-globals |
-| [prefix](variables.tf#L131) | Unique prefix used for resource names. Not used for projects if 'project_create' is null. | string | ✓ | | 00-globals |
-| [composer_config](variables.tf#L34) | Cloud Composer configuration options. | object({…}) | | {…} | |
-| [data_catalog_tags](variables.tf#L48) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {…} | |
-| [data_force_destroy](variables.tf#L59) | Flag to set 'force_destroy' on data services like BigQery or Cloud Storage. | bool | | false | |
-| [groups](variables.tf#L73) | Groups. | map(string) | | {…} | |
-| [location](variables.tf#L91) | Location used for multi-regional resources. | string | | "eu" | |
-| [network_config_composer](variables.tf#L97) | Network configurations to use for Composer. | object({…}) | | {…} | |
-| [outputs_location](variables.tf#L125) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | |
-| [project_services](variables.tf#L137) | List of core services enabled on all projects. | list(string) | | […] | |
-| [region](variables.tf#L148) | Region used for regional resources. | string | | "europe-west1" | |
-| [service_encryption_keys](variables.tf#L154) | Cloud KMS to use to encrypt different services. Key location should match service region. | object({…}) | | null | |
-| [subnet_self_links](variables.tf#L166) | Shared VPC subnet self links. | object({…}) | | null | 02-networking |
-| [vpc_self_links](variables.tf#L175) | Shared VPC self links. | object({…}) | | null | 02-networking |
-
-## Outputs
-
-| name | description | sensitive | consumers |
-|---|---|:---:|---|
-| [bigquery_datasets](outputs.tf#L42) | BigQuery datasets. | | |
-| [demo_commands](outputs.tf#L47) | Demo commands. | | |
-| [gcs_buckets](outputs.tf#L52) | GCS buckets. | | |
-| [kms_keys](outputs.tf#L57) | Cloud MKS keys. | | |
-| [projects](outputs.tf#L62) | GCP Projects informations. | | |
-| [vpc_network](outputs.tf#L67) | VPC network. | | |
-| [vpc_subnet](outputs.tf#L72) | VPC subnetworks. | | |
-
-
diff --git a/fast/stages/01-resman/IAM.md b/fast/stages/1-resman/IAM.md
similarity index 100%
rename from fast/stages/01-resman/IAM.md
rename to fast/stages/1-resman/IAM.md
diff --git a/fast/stages/01-resman/README.md b/fast/stages/1-resman/README.md
similarity index 53%
rename from fast/stages/01-resman/README.md
rename to fast/stages/1-resman/README.md
index 56772816a..971c69633 100644
--- a/fast/stages/01-resman/README.md
+++ b/fast/stages/1-resman/README.md
@@ -13,6 +13,22 @@ The following diagram is a high level reference of the resources created and man
organization | google_billing_account_iam_member |
-| [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder · gcs · iam-service-account | |
+| [billing.tf](./billing.tf) | Billing resources for external billing use cases. | | google_billing_account_iam_member |
+| [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder · gcs · iam-service-account | google_organization_iam_member |
| [branch-gke.tf](./branch-gke.tf) | GKE multitenant stage resources. | folder · gcs · iam-service-account | |
| [branch-networking.tf](./branch-networking.tf) | Networking stage resources. | folder · gcs · iam-service-account | |
-| [branch-project-factory.tf](./branch-project-factory.tf) | Project factory stage resources. | gcs · iam-service-account | |
+| [branch-project-factory.tf](./branch-project-factory.tf) | Project factory stage resources. | gcs · iam-service-account | google_organization_iam_member |
| [branch-sandbox.tf](./branch-sandbox.tf) | Sandbox stage resources. | folder · gcs · iam-service-account | |
| [branch-security.tf](./branch-security.tf) | Security stage resources. | folder · gcs · iam-service-account | |
| [branch-teams.tf](./branch-teams.tf) | Team stage resources. | folder · gcs · iam-service-account | |
@@ -170,7 +188,7 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
| [cicd-project-factory.tf](./cicd-project-factory.tf) | CI/CD resources for the teams branch. | iam-service-account · source-repository | |
| [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | iam-service-account · source-repository | |
| [main.tf](./main.tf) | Module-level locals and resources. | | |
-| [organization.tf](./organization.tf) | Organization policies. | organization | google_organization_iam_member |
+| [organization.tf](./organization.tf) | Organization policies. | organization | |
| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | local_file |
| [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | google_storage_bucket_object |
| [outputs.tf](./outputs.tf) | Module outputs. | | |
@@ -180,34 +198,34 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap |
-| [billing_account](variables.tf#L38) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap |
-| [organization](variables.tf#L197) | Organization details. | object({…}) | ✓ | | 00-bootstrap |
-| [prefix](variables.tf#L221) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap |
-| [cicd_repositories](variables.tf#L47) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | |
-| [custom_roles](variables.tf#L129) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 00-bootstrap |
-| [data_dir](variables.tf#L138) | Relative path for the folder storing configuration data. | string | | "data" | |
-| [fast_features](variables.tf#L144) | Selective control for top-level FAST features. | object({…}) | | {…} | 00-bootstrap |
-| [groups](variables.tf#L164) | Group names to grant organization-level permissions. | map(string) | | {…} | 00-bootstrap |
-| [locations](variables.tf#L179) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | 00-bootstrap |
-| [organization_policy_configs](variables.tf#L207) | Organization policies customization. | object({…}) | | null | |
-| [outputs_location](variables.tf#L215) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | |
-| [tag_names](variables.tf#L232) | Customized names for resource management tags. | object({…}) | | {…} | |
-| [team_folders](variables.tf#L249) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | |
+| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap |
+| [billing_account](variables.tf#L38) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap |
+| [organization](variables.tf#L193) | Organization details. | object({…}) | ✓ | | 0-bootstrap |
+| [prefix](variables.tf#L217) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap |
+| [cicd_repositories](variables.tf#L51) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | |
+| [custom_roles](variables.tf#L133) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap |
+| [data_dir](variables.tf#L142) | Relative path for the folder storing configuration data. | string | | "data" | |
+| [fast_features](variables.tf#L148) | Selective control for top-level FAST features. | object({…}) | | {} | 0-0-bootstrap |
+| [groups](variables.tf#L162) | Group names to grant organization-level permissions. | object({…}) | | {} | 0-bootstrap |
+| [locations](variables.tf#L175) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {…} | 0-bootstrap |
+| [organization_policy_configs](variables.tf#L203) | Organization policies customization. | object({…}) | | null | |
+| [outputs_location](variables.tf#L211) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | |
+| [tag_names](variables.tf#L228) | Customized names for resource management tags. | object({…}) | | {…} | |
+| [team_folders](variables.tf#L247) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | |
## Outputs
| name | description | sensitive | consumers |
|---|---|:---:|---|
-| [cicd_repositories](outputs.tf#L197) | WIF configuration for CI/CD repositories. | | |
-| [dataplatform](outputs.tf#L211) | Data for the Data Platform stage. | | |
-| [gke_multitenant](outputs.tf#L227) | Data for the GKE multitenant stage. | | 03-gke-multitenant |
-| [networking](outputs.tf#L248) | Data for the networking stage. | | |
-| [project_factories](outputs.tf#L257) | Data for the project factories stage. | | |
-| [providers](outputs.tf#L272) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · 03-dataplatform · xx-sandbox · xx-teams |
-| [sandbox](outputs.tf#L279) | Data for the sandbox stage. | | xx-sandbox |
-| [security](outputs.tf#L293) | Data for the networking stage. | | 02-security |
-| [teams](outputs.tf#L303) | Data for the teams stage. | | |
-| [tfvars](outputs.tf#L315) | Terraform variable files for the following stages. | ✓ | |
+| [cicd_repositories](outputs.tf#L210) | WIF configuration for CI/CD repositories. | | |
+| [dataplatform](outputs.tf#L224) | Data for the Data Platform stage. | | |
+| [gke_multitenant](outputs.tf#L240) | Data for the GKE multitenant stage. | | 03-gke-multitenant |
+| [networking](outputs.tf#L261) | Data for the networking stage. | | |
+| [project_factories](outputs.tf#L270) | Data for the project factories stage. | | |
+| [providers](outputs.tf#L285) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · 03-dataplatform · xx-sandbox · xx-teams |
+| [sandbox](outputs.tf#L292) | Data for the sandbox stage. | | xx-sandbox |
+| [security](outputs.tf#L306) | Data for the networking stage. | | 02-security |
+| [teams](outputs.tf#L316) | Data for the teams stage. | | |
+| [tfvars](outputs.tf#L328) | Terraform variable files for the following stages. | ✓ | |
diff --git a/fast/stages/01-resman/billing.tf b/fast/stages/1-resman/billing.tf
similarity index 77%
rename from fast/stages/01-resman/billing.tf
rename to fast/stages/1-resman/billing.tf
index fe497c7c3..ba20ab053 100644
--- a/fast/stages/01-resman/billing.tf
+++ b/fast/stages/1-resman/billing.tf
@@ -34,23 +34,11 @@ locals {
# billing account in same org (resources is in the organization.tf file)
-# billing account in a different org
-
-module "billing-organization-ext" {
- source = "../../../modules/organization"
- count = local.billing_org_ext ? 1 : 0
- organization_id = "organizations/${var.billing_account.organization_id}"
- iam_additive = {
- "roles/billing.user" = local.billing_ext_users
- "roles/billing.costsManager" = local.billing_ext_users
- }
-}
-
# standalone billing account
resource "google_billing_account_iam_member" "billing_ext_admin" {
for_each = toset(
- local.billing_ext ? local.billing_ext_users : []
+ !var.billing_account.is_org_level ? local.billing_ext_users : []
)
billing_account_id = var.billing_account.id
role = "roles/billing.user"
@@ -59,7 +47,7 @@ resource "google_billing_account_iam_member" "billing_ext_admin" {
resource "google_billing_account_iam_member" "billing_ext_costsmanager" {
for_each = toset(
- local.billing_ext ? local.billing_ext_users : []
+ !var.billing_account.is_org_level ? local.billing_ext_users : []
)
billing_account_id = var.billing_account.id
role = "roles/billing.costsManager"
diff --git a/fast/stages/01-resman/branch-data-platform.tf b/fast/stages/1-resman/branch-data-platform.tf
similarity index 86%
rename from fast/stages/01-resman/branch-data-platform.tf
rename to fast/stages/1-resman/branch-data-platform.tf
index 66cc9fbb0..7a93e7a54 100644
--- a/fast/stages/01-resman/branch-data-platform.tf
+++ b/fast/stages/1-resman/branch-data-platform.tf
@@ -137,3 +137,22 @@ module "branch-dp-prod-gcs" {
"roles/storage.objectAdmin" = [module.branch-dp-prod-sa.0.iam_email]
}
}
+
+resource "google_organization_iam_member" "org_policy_admin_dp" {
+ for_each = !var.fast_features.data_platform ? {} : {
+ data-dev = ["data", "development", module.branch-dp-dev-sa.0.iam_email]
+ data-prod = ["data", "production", module.branch-dp-prod-sa.0.iam_email]
+ }
+ org_id = var.organization.id
+ role = "roles/orgpolicy.policyAdmin"
+ member = each.value.2
+ condition {
+ title = "org_policy_tag_dp_scoped"
+ description = "Org policy tag scoped grant for ${each.value.0}/${each.value.1}."
+ expression = <<-END
+ resource.matchTag('${var.organization.id}/${var.tag_names.context}', '${each.value.0}')
+ &&
+ resource.matchTag('${var.organization.id}/${var.tag_names.environment}', '${each.value.1}')
+ END
+ }
+}
diff --git a/fast/stages/01-resman/branch-gke.tf b/fast/stages/1-resman/branch-gke.tf
similarity index 94%
rename from fast/stages/01-resman/branch-gke.tf
rename to fast/stages/1-resman/branch-gke.tf
index 84ca41ed5..76777d8fc 100644
--- a/fast/stages/01-resman/branch-gke.tf
+++ b/fast/stages/1-resman/branch-gke.tf
@@ -77,7 +77,11 @@ module "branch-gke-dev-sa" {
prefix = var.prefix
iam = {
"roles/iam.serviceAccountTokenCreator" = concat(
- ["group:${local.groups.gcp-devops}"],
+ (
+ local.groups.gcp-devops == null
+ ? []
+ : ["group:${local.groups.gcp-devops}"]
+ ),
compact([
try(module.branch-gke-dev-sa-cicd.0.iam_email, null)
])
@@ -97,7 +101,11 @@ module "branch-gke-prod-sa" {
prefix = var.prefix
iam = {
"roles/iam.serviceAccountTokenCreator" = concat(
- ["group:${local.groups.gcp-devops}"],
+ (
+ local.groups.gcp-devops == null
+ ? []
+ : ["group:${local.groups.gcp-devops}"]
+ ),
compact([
try(module.branch-gke-prod-sa-cicd.0.iam_email, null)
])
diff --git a/fast/stages/01-resman/branch-networking.tf b/fast/stages/1-resman/branch-networking.tf
similarity index 98%
rename from fast/stages/01-resman/branch-networking.tf
rename to fast/stages/1-resman/branch-networking.tf
index 530cf6b09..1fd7a6b3d 100644
--- a/fast/stages/01-resman/branch-networking.tf
+++ b/fast/stages/1-resman/branch-networking.tf
@@ -20,7 +20,7 @@ module "branch-network-folder" {
source = "../../../modules/folder"
parent = "organizations/${var.organization.id}"
name = "Networking"
- group_iam = {
+ group_iam = local.groups.gcp-network-admins == null ? {} : {
(local.groups.gcp-network-admins) = [
# add any needed roles for resources/services not managed via Terraform,
# or replace editor with ~viewer if no broad resource management needed
diff --git a/fast/stages/01-resman/branch-project-factory.tf b/fast/stages/1-resman/branch-project-factory.tf
similarity index 78%
rename from fast/stages/01-resman/branch-project-factory.tf
rename to fast/stages/1-resman/branch-project-factory.tf
index 41651a28c..d74a8acb1 100644
--- a/fast/stages/01-resman/branch-project-factory.tf
+++ b/fast/stages/1-resman/branch-project-factory.tf
@@ -79,3 +79,22 @@ module "branch-pf-prod-gcs" {
"roles/storage.objectAdmin" = [module.branch-pf-prod-sa.0.iam_email]
}
}
+
+resource "google_organization_iam_member" "org_policy_admin_pf" {
+ for_each = !var.fast_features.project_factory ? {} : {
+ pf-dev = ["teams", "development", module.branch-pf-dev-sa.0.iam_email]
+ pf-prod = ["teams", "production", module.branch-pf-prod-sa.0.iam_email]
+ }
+ org_id = var.organization.id
+ role = "roles/orgpolicy.policyAdmin"
+ member = each.value.2
+ condition {
+ title = "org_policy_tag_pf_scoped"
+ description = "Org policy tag scoped grant for ${each.value.0}/${each.value.1}."
+ expression = <<-END
+ resource.matchTag('${var.organization.id}/${var.tag_names.context}', '${each.value.0}')
+ &&
+ resource.matchTag('${var.organization.id}/${var.tag_names.environment}', '${each.value.1}')
+ END
+ }
+}
diff --git a/fast/stages/01-resman/branch-sandbox.tf b/fast/stages/1-resman/branch-sandbox.tf
similarity index 100%
rename from fast/stages/01-resman/branch-sandbox.tf
rename to fast/stages/1-resman/branch-sandbox.tf
diff --git a/fast/stages/01-resman/branch-security.tf b/fast/stages/1-resman/branch-security.tf
similarity index 96%
rename from fast/stages/01-resman/branch-security.tf
rename to fast/stages/1-resman/branch-security.tf
index c7b4fc970..4b0c0fb13 100644
--- a/fast/stages/01-resman/branch-security.tf
+++ b/fast/stages/1-resman/branch-security.tf
@@ -20,7 +20,7 @@ module "branch-security-folder" {
source = "../../../modules/folder"
parent = "organizations/${var.organization.id}"
name = "Security"
- group_iam = {
+ group_iam = local.groups.gcp-security-admins == null ? {} : {
(local.groups.gcp-security-admins) = [
# add any needed roles for resources/services not managed via Terraform,
# e.g.
@@ -51,7 +51,7 @@ module "branch-security-folder" {
module "branch-security-sa" {
source = "../../../modules/iam-service-account"
project_id = var.automation.project_id
- name = "prod-resman-sec-0"
+ name = "security-0"
display_name = "Terraform resman security service account."
prefix = var.prefix
iam = {
diff --git a/fast/stages/01-resman/branch-teams.tf b/fast/stages/1-resman/branch-teams.tf
similarity index 100%
rename from fast/stages/01-resman/branch-teams.tf
rename to fast/stages/1-resman/branch-teams.tf
diff --git a/fast/stages/01-resman/cicd-data-platform.tf b/fast/stages/1-resman/cicd-data-platform.tf
similarity index 99%
rename from fast/stages/01-resman/cicd-data-platform.tf
rename to fast/stages/1-resman/cicd-data-platform.tf
index 5b07883c4..e69fd5bc1 100644
--- a/fast/stages/01-resman/cicd-data-platform.tf
+++ b/fast/stages/1-resman/cicd-data-platform.tf
@@ -103,7 +103,7 @@ module "branch-dp-dev-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
@@ -146,7 +146,7 @@ module "branch-dp-prod-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
diff --git a/fast/stages/01-resman/cicd-gke.tf b/fast/stages/1-resman/cicd-gke.tf
similarity index 99%
rename from fast/stages/01-resman/cicd-gke.tf
rename to fast/stages/1-resman/cicd-gke.tf
index fa4f8767c..4388a3ac5 100644
--- a/fast/stages/01-resman/cicd-gke.tf
+++ b/fast/stages/1-resman/cicd-gke.tf
@@ -103,7 +103,7 @@ module "branch-gke-dev-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
@@ -146,7 +146,7 @@ module "branch-gke-prod-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
diff --git a/fast/stages/01-resman/cicd-networking.tf b/fast/stages/1-resman/cicd-networking.tf
similarity index 99%
rename from fast/stages/01-resman/cicd-networking.tf
rename to fast/stages/1-resman/cicd-networking.tf
index 894348ff3..245d5ed02 100644
--- a/fast/stages/01-resman/cicd-networking.tf
+++ b/fast/stages/1-resman/cicd-networking.tf
@@ -65,7 +65,7 @@ module "branch-network-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
diff --git a/fast/stages/01-resman/cicd-project-factory.tf b/fast/stages/1-resman/cicd-project-factory.tf
similarity index 99%
rename from fast/stages/01-resman/cicd-project-factory.tf
rename to fast/stages/1-resman/cicd-project-factory.tf
index 8f357ce6c..1e2b45653 100644
--- a/fast/stages/01-resman/cicd-project-factory.tf
+++ b/fast/stages/1-resman/cicd-project-factory.tf
@@ -114,7 +114,7 @@ module "branch-pf-dev-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
@@ -162,7 +162,7 @@ module "branch-pf-prod-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
diff --git a/fast/stages/01-resman/cicd-security.tf b/fast/stages/1-resman/cicd-security.tf
similarity index 99%
rename from fast/stages/01-resman/cicd-security.tf
rename to fast/stages/1-resman/cicd-security.tf
index dd27a4733..c35bfbfbb 100644
--- a/fast/stages/01-resman/cicd-security.tf
+++ b/fast/stages/1-resman/cicd-security.tf
@@ -65,7 +65,7 @@ module "branch-security-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
diff --git a/fast/stages/1-resman/data/org-policies/compute.yaml b/fast/stages/1-resman/data/org-policies/compute.yaml
new file mode 100644
index 000000000..0d27ac426
--- /dev/null
+++ b/fast/stages/1-resman/data/org-policies/compute.yaml
@@ -0,0 +1,73 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+compute.disableGuestAttributesAccess:
+ enforce: true
+
+compute.requireOsLogin:
+ enforce: true
+
+compute.restrictLoadBalancerCreationForTypes:
+ allow:
+ values:
+ - in:INTERNAL
+
+compute.skipDefaultNetworkCreation:
+ enforce: true
+
+compute.vmExternalIpAccess:
+ deny:
+ all: true
+
+
+# compute.disableInternetNetworkEndpointGroup:
+# enforce: true
+
+# compute.disableNestedVirtualization:
+# enforce: true
+
+# compute.disableSerialPortAccess:
+# enforce: true
+
+# compute.restrictCloudNATUsage:
+# deny:
+# all: true
+
+# compute.restrictDedicatedInterconnectUsage:
+# deny:
+# all: true
+
+# compute.restrictPartnerInterconnectUsage:
+# deny:
+# all: true
+
+# compute.restrictProtocolForwardingCreationForTypes:
+# deny:
+# all: true
+
+# compute.restrictSharedVpcHostProjects:
+# deny:
+# all: true
+
+# compute.restrictSharedVpcSubnetworks:
+# deny:
+# all: true
+
+# compute.restrictVpcPeering:
+# deny:
+# all: true
+
+# compute.restrictVpnPeerIPs:
+# deny:
+# all: true
+
+# compute.restrictXpnProjectLienRemoval:
+# enforce: true
+
+# compute.setNewProjectDefaultToZonalDNSOnly:
+# enforce: true
+
+# compute.vmCanIpForward:
+# deny:
+# all: true
diff --git a/fast/stages/1-resman/data/org-policies/iam.yaml b/fast/stages/1-resman/data/org-policies/iam.yaml
new file mode 100644
index 000000000..4d83f827f
--- /dev/null
+++ b/fast/stages/1-resman/data/org-policies/iam.yaml
@@ -0,0 +1,12 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+iam.automaticIamGrantsForDefaultServiceAccounts:
+ enforce: true
+
+iam.disableServiceAccountKeyCreation:
+ enforce: true
+
+iam.disableServiceAccountKeyUpload:
+ enforce: true
diff --git a/fast/stages/1-resman/data/org-policies/serverless.yaml b/fast/stages/1-resman/data/org-policies/serverless.yaml
new file mode 100644
index 000000000..de62e6c70
--- /dev/null
+++ b/fast/stages/1-resman/data/org-policies/serverless.yaml
@@ -0,0 +1,26 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+run.allowedIngress:
+ allow:
+ values:
+ - is:internal
+
+# run.allowedVPCEgress:
+# allow:
+# values:
+# - is:private-ranges-only
+
+# cloudfunctions.allowedIngressSettings:
+# allow:
+# values:
+# - is:ALLOW_INTERNAL_ONLY
+
+# cloudfunctions.allowedVpcConnectorEgressSettings:
+# allow:
+# values:
+# - is:PRIVATE_RANGES_ONLY
+
+# cloudfunctions.requireVPCConnector:
+# enforce: true
diff --git a/fast/stages/1-resman/data/org-policies/sql.yaml b/fast/stages/1-resman/data/org-policies/sql.yaml
new file mode 100644
index 000000000..88b84d9d5
--- /dev/null
+++ b/fast/stages/1-resman/data/org-policies/sql.yaml
@@ -0,0 +1,9 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+sql.restrictAuthorizedNetworks:
+ enforce: true
+
+sql.restrictPublicIp:
+ enforce: true
diff --git a/fast/stages/1-resman/data/org-policies/storage.yaml b/fast/stages/1-resman/data/org-policies/storage.yaml
new file mode 100644
index 000000000..6c0a673f3
--- /dev/null
+++ b/fast/stages/1-resman/data/org-policies/storage.yaml
@@ -0,0 +1,6 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+storage.uniformBucketLevelAccess:
+ enforce: true
diff --git a/fast/stages/1-resman/diagram.png b/fast/stages/1-resman/diagram.png
new file mode 100644
index 000000000..d1026318b
Binary files /dev/null and b/fast/stages/1-resman/diagram.png differ
diff --git a/fast/stages/1-resman/diagram.svg b/fast/stages/1-resman/diagram.svg
new file mode 100644
index 000000000..541db3f4b
--- /dev/null
+++ b/fast/stages/1-resman/diagram.svg
@@ -0,0 +1,1340 @@
+
+google_monitoring_dashboard |
| [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file |
| [peerings.tf](./peerings.tf) | None | net-vpc-peering | |
+| [regions.tf](./regions.tf) | Compute short names for regions. | | |
| [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 | |
@@ -295,23 +330,23 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap |
-| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap |
-| [folder_ids](variables.tf#L74) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 01-resman |
-| [organization](variables.tf#L102) | Organization details. | object({…}) | ✓ | | 00-bootstrap |
-| [prefix](variables.tf#L118) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap |
-| [custom_adv](variables.tf#L34) | Custom advertisement definitions in name => range format. | map(string) | | {…} | |
-| [custom_roles](variables.tf#L51) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 00-bootstrap |
-| [data_dir](variables.tf#L60) | Relative path for the folder storing configuration data for network resources. | string | | "data" | |
-| [dns](variables.tf#L66) | Onprem DNS resolvers. | map(list(string)) | | {…} | |
-| [l7ilb_subnets](variables.tf#L84) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | |
-| [outputs_location](variables.tf#L112) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | |
+| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap |
+| [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap |
+| [folder_ids](variables.tf#L92) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 1-resman |
+| [organization](variables.tf#L126) | Organization details. | object({…}) | ✓ | | 0-bootstrap |
+| [prefix](variables.tf#L142) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap |
+| [custom_adv](variables.tf#L38) | Custom advertisement definitions in name => range format. | map(string) | | {…} | |
+| [custom_roles](variables.tf#L55) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap |
+| [dns](variables.tf#L64) | Onprem DNS resolvers. | map(list(string)) | | {…} | |
+| [factories_config](variables.tf#L72) | Configuration for network resource factories. | object({…}) | | {…} | |
+| [l7ilb_subnets](variables.tf#L102) | Subnets used for L7 ILBs. | object({…}) | | {…} | |
+| [outputs_location](variables.tf#L136) | 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#L129) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | |
-| [region_trigram](variables.tf#L166) | Short names for GCP regions. | map(string) | | {…} | |
-| [router_onprem_configs](variables.tf#L175) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | |
-| [service_accounts](variables.tf#L193) | Automation service accounts in name => email format. | object({…}) | | null | 01-resman |
-| [vpn_onprem_configs](variables.tf#L207) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | |
+| [psa_ranges](variables.tf#L153) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | |
+| [regions](variables.tf#L190) | Region definitions. | object({…}) | | {…} | |
+| [router_onprem_configs](variables.tf#L202) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | |
+| [service_accounts](variables.tf#L220) | Automation service accounts in name => email format. | object({…}) | | null | 1-resman |
+| [vpn_onprem_configs](variables.tf#L234) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | |
## Outputs
diff --git a/fast/stages/02-networking-nva/data/cidrs.yaml b/fast/stages/2-networking-a-peering/data/cidrs.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/cidrs.yaml
rename to fast/stages/2-networking-a-peering/data/cidrs.yaml
diff --git a/fast/stages/02-networking-peering/data/dashboards/firewall_insights.json b/fast/stages/2-networking-a-peering/data/dashboards/firewall_insights.json
similarity index 100%
rename from fast/stages/02-networking-peering/data/dashboards/firewall_insights.json
rename to fast/stages/2-networking-a-peering/data/dashboards/firewall_insights.json
diff --git a/fast/stages/02-networking-peering/data/dashboards/vpn.json b/fast/stages/2-networking-a-peering/data/dashboards/vpn.json
similarity index 100%
rename from fast/stages/02-networking-peering/data/dashboards/vpn.json
rename to fast/stages/2-networking-a-peering/data/dashboards/vpn.json
diff --git a/fast/stages/02-networking-nva/data/firewall-rules/dev/rules.yaml b/fast/stages/2-networking-a-peering/data/firewall-rules/dev/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/firewall-rules/dev/rules.yaml
rename to fast/stages/2-networking-a-peering/data/firewall-rules/dev/rules.yaml
diff --git a/fast/stages/02-networking-peering/data/firewall-rules/landing/rules.yaml b/fast/stages/2-networking-a-peering/data/firewall-rules/landing/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/firewall-rules/landing/rules.yaml
rename to fast/stages/2-networking-a-peering/data/firewall-rules/landing/rules.yaml
diff --git a/fast/stages/02-networking-nva/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-a-peering/data/hierarchical-policy-rules.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/hierarchical-policy-rules.yaml
rename to fast/stages/2-networking-a-peering/data/hierarchical-policy-rules.yaml
diff --git a/fast/stages/02-networking-peering/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/subnets/dev/dev-dataplatform-ew1.yaml
rename to fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml
diff --git a/fast/stages/02-networking-peering/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/subnets/dev/dev-default-ew1.yaml
rename to fast/stages/2-networking-a-peering/data/subnets/dev/dev-default-ew1.yaml
diff --git a/fast/stages/02-networking-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml
rename to fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml
diff --git a/fast/stages/02-networking-peering/data/subnets/landing/landing-default-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/landing/landing-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/subnets/landing/landing-default-ew1.yaml
rename to fast/stages/2-networking-a-peering/data/subnets/landing/landing-default-ew1.yaml
diff --git a/fast/stages/02-networking-peering/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/prod/prod-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/subnets/prod/prod-default-ew1.yaml
rename to fast/stages/2-networking-a-peering/data/subnets/prod/prod-default-ew1.yaml
diff --git a/fast/stages/02-networking-peering/diagram.png b/fast/stages/2-networking-a-peering/diagram.png
similarity index 100%
rename from fast/stages/02-networking-peering/diagram.png
rename to fast/stages/2-networking-a-peering/diagram.png
diff --git a/fast/stages/02-networking-peering/diagram.svg b/fast/stages/2-networking-a-peering/diagram.svg
similarity index 100%
rename from fast/stages/02-networking-peering/diagram.svg
rename to fast/stages/2-networking-a-peering/diagram.svg
diff --git a/fast/stages/02-networking-peering/dns-dev.tf b/fast/stages/2-networking-a-peering/dns-dev.tf
similarity index 100%
rename from fast/stages/02-networking-peering/dns-dev.tf
rename to fast/stages/2-networking-a-peering/dns-dev.tf
diff --git a/fast/stages/02-networking-peering/dns-landing.tf b/fast/stages/2-networking-a-peering/dns-landing.tf
similarity index 100%
rename from fast/stages/02-networking-peering/dns-landing.tf
rename to fast/stages/2-networking-a-peering/dns-landing.tf
diff --git a/fast/stages/02-networking-peering/dns-prod.tf b/fast/stages/2-networking-a-peering/dns-prod.tf
similarity index 100%
rename from fast/stages/02-networking-peering/dns-prod.tf
rename to fast/stages/2-networking-a-peering/dns-prod.tf
diff --git a/fast/stages/02-networking-peering/landing.tf b/fast/stages/2-networking-a-peering/landing.tf
similarity index 82%
rename from fast/stages/02-networking-peering/landing.tf
rename to fast/stages/2-networking-a-peering/landing.tf
index 83a0d509a..37e3adfd4 100644
--- a/fast/stages/02-networking-peering/landing.tf
+++ b/fast/stages/2-networking-a-peering/landing.tf
@@ -63,7 +63,7 @@ module "landing-vpc" {
next_hop = "default-internet-gateway"
}
}
- data_folder = "${var.data_dir}/subnets/landing"
+ data_folder = "${var.factories_config.data_dir}/subnets/landing"
}
module "landing-firewall" {
@@ -74,18 +74,23 @@ module "landing-firewall" {
disabled = true
}
factories_config = {
- cidr_tpl_file = "${var.data_dir}/cidrs.yaml"
- rules_folder = "${var.data_dir}/firewall-rules/landing"
+ cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ rules_folder = "${var.factories_config.data_dir}/firewall-rules/landing"
}
}
-module "landing-nat-ew1" {
+moved {
+ from = module.landing-nat-ew1
+ to = module.landing-nat-primary
+}
+
+module "landing-nat-primary" {
source = "../../../modules/net-cloudnat"
project_id = module.landing-project.project_id
- region = "europe-west1"
- name = "ew1"
+ region = var.regions.primary
+ name = local.region_shortnames[var.regions.primary]
router_create = true
- router_name = "prod-nat-ew1"
+ router_name = "prod-nat-${local.region_shortnames[var.regions.primary]}"
router_network = module.landing-vpc.name
router_asn = 4200001024
}
diff --git a/fast/stages/02-networking-vpn/main.tf b/fast/stages/2-networking-a-peering/main.tf
similarity index 76%
rename from fast/stages/02-networking-vpn/main.tf
rename to fast/stages/2-networking-a-peering/main.tf
index f68d39eb8..5888752d7 100644
--- a/fast/stages/02-networking-vpn/main.tf
+++ b/fast/stages/2-networking-a-peering/main.tf
@@ -17,14 +17,14 @@
# tfdoc:file:description Networking folder and hierarchical policy.
locals {
+ # combine all regions from variables and subnets
+ regions = distinct(concat(
+ values(var.regions),
+ values(module.dev-spoke-vpc.subnet_regions),
+ values(module.landing-vpc.subnet_regions),
+ values(module.prod-spoke-vpc.subnet_regions),
+ ))
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}"
- })]
- }
stage3_sas_delegated_grants = [
"roles/composer.sharedVpcAgent",
"roles/compute.networkUser",
@@ -46,9 +46,9 @@ module "folder" {
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"
+ cidr_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ policy_name = var.factories_config.firewall_policy_name
+ rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml"
}
firewall_policy_association = {
factory-policy = "factory"
diff --git a/fast/stages/02-networking-vpn/monitoring.tf b/fast/stages/2-networking-a-peering/monitoring.tf
similarity index 93%
rename from fast/stages/02-networking-vpn/monitoring.tf
rename to fast/stages/2-networking-a-peering/monitoring.tf
index 7b8b70c51..be3a47faa 100644
--- a/fast/stages/02-networking-vpn/monitoring.tf
+++ b/fast/stages/2-networking-a-peering/monitoring.tf
@@ -17,7 +17,7 @@
# tfdoc:file:description Network monitoring dashboards.
locals {
- dashboard_path = "${var.data_dir}/dashboards"
+ dashboard_path = "${var.factories_config.data_dir}/dashboards"
dashboard_files = fileset(local.dashboard_path, "*.json")
dashboards = {
for filename in local.dashboard_files :
diff --git a/fast/stages/02-networking-peering/outputs.tf b/fast/stages/2-networking-a-peering/outputs.tf
similarity index 93%
rename from fast/stages/02-networking-peering/outputs.tf
rename to fast/stages/2-networking-a-peering/outputs.tf
index 3b97b7f25..628c706b3 100644
--- a/fast/stages/02-networking-peering/outputs.tf
+++ b/fast/stages/2-networking-a-peering/outputs.tf
@@ -48,13 +48,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/02-networking.auto.tfvars.json"
+ filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/02-networking.auto.tfvars.json"
+ name = "tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
@@ -89,8 +89,8 @@ output "tfvars" {
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 :
+ onprem-primary = {
+ for v in module.landing-to-onprem-primary-vpn[0].gateway.vpn_interfaces :
v.id => v.ip_address
}
}
diff --git a/fast/stages/02-networking-peering/peerings.tf b/fast/stages/2-networking-a-peering/peerings.tf
similarity index 100%
rename from fast/stages/02-networking-peering/peerings.tf
rename to fast/stages/2-networking-a-peering/peerings.tf
diff --git a/fast/stages/2-networking-a-peering/regions.tf b/fast/stages/2-networking-a-peering/regions.tf
new file mode 100644
index 000000000..53514afa9
--- /dev/null
+++ b/fast/stages/2-networking-a-peering/regions.tf
@@ -0,0 +1,42 @@
+/**
+ * Copyright 2023 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 Compute short names for regions.
+
+locals {
+ # only map when the first character would not work
+ _region_cardinal = {
+ southeast = "se"
+ }
+ # only map when the first character would not work
+ _region_geo = {
+ australia = "o"
+ }
+ # split in [geo, cardinal, number] tokens
+ _region_tokens = {
+ for v in local.regions : v => regexall("(?:[a-z]+)|(?:[0-9]+)", v)
+ }
+ region_shortnames = {
+ for k, v in local._region_tokens : k => join("", [
+ # first token via geo alias map or first character
+ lookup(local._region_geo, v.0, substr(v.0, 0, 1)),
+ # first token via cardinal alias map or first character
+ lookup(local._region_cardinal, v.1, substr(v.1, 0, 1)),
+ # region number as is
+ v.2
+ ])
+ }
+}
diff --git a/fast/stages/02-networking-peering/spoke-dev.tf b/fast/stages/2-networking-a-peering/spoke-dev.tf
similarity index 84%
rename from fast/stages/02-networking-peering/spoke-dev.tf
rename to fast/stages/2-networking-a-peering/spoke-dev.tf
index e67cfb70d..ed7d13cb0 100644
--- a/fast/stages/02-networking-peering/spoke-dev.tf
+++ b/fast/stages/2-networking-a-peering/spoke-dev.tf
@@ -16,6 +16,19 @@
# tfdoc:file:description Dev spoke VPC and related resources.
+locals {
+ _l7ilb_subnets_dev = [
+ for v in var.l7ilb_subnets.dev : merge(v, {
+ active = true
+ region = lookup(var.regions, v.region, v.region)
+ })]
+ l7ilb_subnets_dev = [
+ for v in local._l7ilb_subnets_dev : merge(v, {
+ name = "dev-l7ilb-${local.region_shortnames[v.region]}"
+ })
+ ]
+}
+
module "dev-spoke-project" {
source = "../../../modules/project"
billing_account = var.billing_account.id
@@ -48,9 +61,9 @@ module "dev-spoke-vpc" {
project_id = module.dev-spoke-project.project_id
name = "dev-spoke-0"
mtu = 1500
- data_folder = "${var.data_dir}/subnets/dev"
+ data_folder = "${var.factories_config.data_dir}/subnets/dev"
psa_config = try(var.psa_ranges.dev, null)
- subnets_proxy_only = local.l7ilb_subnets.dev
+ subnets_proxy_only = local.l7ilb_subnets_dev
# set explicit routes for googleapis in case the default route is deleted
routes = {
private-googleapis = {
@@ -74,8 +87,8 @@ module "dev-spoke-firewall" {
disabled = true
}
factories_config = {
- cidr_tpl_file = "${var.data_dir}/cidrs.yaml"
- rules_folder = "${var.data_dir}/firewall-rules/dev"
+ cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ rules_folder = "${var.factories_config.data_dir}/firewall-rules/dev"
}
}
@@ -84,7 +97,7 @@ module "dev-spoke-cloudnat" {
source = "../../../modules/net-cloudnat"
project_id = module.dev-spoke-project.project_id
region = each.value
- name = "dev-nat-${var.region_trigram[each.value]}"
+ name = "dev-nat-${local.region_shortnames[each.value]}"
router_create = true
router_network = module.dev-spoke-vpc.name
router_asn = 4200001024
diff --git a/fast/stages/02-networking-vpn/spoke-prod.tf b/fast/stages/2-networking-a-peering/spoke-prod.tf
similarity index 84%
rename from fast/stages/02-networking-vpn/spoke-prod.tf
rename to fast/stages/2-networking-a-peering/spoke-prod.tf
index cf49152fa..f584b32da 100644
--- a/fast/stages/02-networking-vpn/spoke-prod.tf
+++ b/fast/stages/2-networking-a-peering/spoke-prod.tf
@@ -16,6 +16,19 @@
# tfdoc:file:description Production spoke VPC and related resources.
+locals {
+ _l7ilb_subnets_prod = [
+ for v in var.l7ilb_subnets.prod : merge(v, {
+ active = true
+ region = lookup(var.regions, v.region, v.region)
+ })]
+ l7ilb_subnets_prod = [
+ for v in local._l7ilb_subnets_prod : merge(v, {
+ name = "prod-l7ilb-${local.region_shortnames[v.region]}"
+ })
+ ]
+}
+
module "prod-spoke-project" {
source = "../../../modules/project"
billing_account = var.billing_account.id
@@ -48,9 +61,9 @@ module "prod-spoke-vpc" {
project_id = module.prod-spoke-project.project_id
name = "prod-spoke-0"
mtu = 1500
- data_folder = "${var.data_dir}/subnets/prod"
+ data_folder = "${var.factories_config.data_dir}/subnets/prod"
psa_config = try(var.psa_ranges.prod, null)
- subnets_proxy_only = local.l7ilb_subnets.prod
+ subnets_proxy_only = local.l7ilb_subnets_prod
# set explicit routes for googleapis in case the default route is deleted
routes = {
private-googleapis = {
@@ -74,8 +87,8 @@ module "prod-spoke-firewall" {
disabled = true
}
factories_config = {
- cidr_tpl_file = "${var.data_dir}/cidrs.yaml"
- rules_folder = "${var.data_dir}/firewall-rules/prod"
+ cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ rules_folder = "${var.factories_config.data_dir}/firewall-rules/prod"
}
}
@@ -84,7 +97,7 @@ module "prod-spoke-cloudnat" {
source = "../../../modules/net-cloudnat"
project_id = module.prod-spoke-project.project_id
region = each.value
- name = "prod-nat-${var.region_trigram[each.value]}"
+ name = "prod-nat-${local.region_shortnames[each.value]}"
router_create = true
router_network = module.prod-spoke-vpc.name
router_asn = 4200001024
diff --git a/fast/stages/02-networking-peering/test-resources.tf b/fast/stages/2-networking-a-peering/test-resources.tf
similarity index 76%
rename from fast/stages/02-networking-peering/test-resources.tf
rename to fast/stages/2-networking-a-peering/test-resources.tf
index 204971fec..67073845d 100644
--- a/fast/stages/02-networking-peering/test-resources.tf
+++ b/fast/stages/2-networking-a-peering/test-resources.tf
@@ -19,11 +19,11 @@
# module "test-vm-landing-0" {
# source = "../../../modules/compute-vm"
# project_id = module.landing-project.project_id
-# zone = "europe-west1-b"
+# zone = "${var.regions.primary}-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"]
+# subnetwork = module.landing-vpc.subnet_self_links["${var.regions.primary}/landing-default-${local.region_shortnames[var.regions.primary]}"]
# }]
# tags = ["ssh"]
# service_account_create = true
@@ -31,8 +31,8 @@
# image = "projects/debian-cloud/global/images/family/debian-10"
# }
# options = {
-# spot = true
-# termination_action = "STOP"
+# spot = true
+# termination_action = "STOP"
# }
# metadata = {
# startup-script = <folder | |
| [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard |
| [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file |
+| [regions.tf](./regions.tf) | Compute short names for regions. | | |
| [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 | |
@@ -313,31 +348,31 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [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 | |
-| [vpn-spoke-prod-ew1.tf](./vpn-spoke-prod-ew1.tf) | VPN between landing and production spoke in ew1. | net-vpn-ha | |
-| [vpn-spoke-prod-ew4.tf](./vpn-spoke-prod-ew4.tf) | VPN between landing and production spoke in ew4. | net-vpn-ha | |
+| [vpn-spoke-prod-primary.tf](./vpn-spoke-prod-primary.tf) | VPN between landing and production spoke in ew1. | net-vpn-ha | |
+| [vpn-spoke-prod-secondary.tf](./vpn-spoke-prod-secondary.tf) | VPN between landing and production spoke in ew4. | net-vpn-ha | |
## Variables
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap |
-| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap |
-| [folder_ids](variables.tf#L74) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 01-resman |
-| [organization](variables.tf#L102) | Organization details. | object({…}) | ✓ | | 00-bootstrap |
-| [prefix](variables.tf#L118) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap |
-| [custom_adv](variables.tf#L34) | Custom advertisement definitions in name => range format. | map(string) | | {…} | |
-| [custom_roles](variables.tf#L51) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 00-bootstrap |
-| [data_dir](variables.tf#L60) | Relative path for the folder storing configuration data for network resources. | string | | "data" | |
-| [dns](variables.tf#L66) | Onprem DNS resolvers. | map(list(string)) | | {…} | |
-| [l7ilb_subnets](variables.tf#L84) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | |
-| [outputs_location](variables.tf#L112) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | |
-| [psa_ranges](variables.tf#L129) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | |
-| [region_trigram](variables.tf#L166) | Short names for GCP regions. | map(string) | | {…} | |
-| [router_onprem_configs](variables.tf#L175) | 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#L193) | Automation service accounts in name => email format. | object({…}) | | null | 01-resman |
-| [vpn_onprem_configs](variables.tf#L207) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | |
-| [vpn_spoke_configs](variables-vpn.tf#L37) | VPN gateway configuration for spokes. | map(object({…})) | | {…} | |
+| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap |
+| [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap |
+| [folder_ids](variables.tf#L92) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 1-resman |
+| [organization](variables.tf#L126) | Organization details. | object({…}) | ✓ | | 0-bootstrap |
+| [prefix](variables.tf#L142) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap |
+| [custom_adv](variables.tf#L38) | Custom advertisement definitions in name => range format. | map(string) | | {…} | |
+| [custom_roles](variables.tf#L55) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap |
+| [dns](variables.tf#L64) | Onprem DNS resolvers. | map(list(string)) | | {…} | |
+| [factories_config](variables.tf#L72) | Configuration for network resource factories. | object({…}) | | {…} | |
+| [l7ilb_subnets](variables.tf#L102) | Subnets used for L7 ILBs. | object({…}) | | {…} | |
+| [outputs_location](variables.tf#L136) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | |
+| [psa_ranges](variables.tf#L153) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | |
+| [regions](variables.tf#L190) | Region definitions. | object({…}) | | {…} | |
+| [router_onprem_configs](variables.tf#L202) | 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#L220) | Automation service accounts in name => email format. | object({…}) | | null | 1-resman |
+| [vpn_onprem_configs](variables.tf#L234) | 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-peering/data/cidrs.yaml b/fast/stages/2-networking-b-vpn/data/cidrs.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/cidrs.yaml
rename to fast/stages/2-networking-b-vpn/data/cidrs.yaml
diff --git a/fast/stages/02-networking-separate-envs/data/dashboards/firewall_insights.json b/fast/stages/2-networking-b-vpn/data/dashboards/firewall_insights.json
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/dashboards/firewall_insights.json
rename to fast/stages/2-networking-b-vpn/data/dashboards/firewall_insights.json
diff --git a/fast/stages/02-networking-separate-envs/data/dashboards/vpn.json b/fast/stages/2-networking-b-vpn/data/dashboards/vpn.json
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/dashboards/vpn.json
rename to fast/stages/2-networking-b-vpn/data/dashboards/vpn.json
diff --git a/fast/stages/02-networking-peering/data/firewall-rules/dev/rules.yaml b/fast/stages/2-networking-b-vpn/data/firewall-rules/dev/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/firewall-rules/dev/rules.yaml
rename to fast/stages/2-networking-b-vpn/data/firewall-rules/dev/rules.yaml
diff --git a/fast/stages/02-networking-vpn/data/firewall-rules/landing/rules.yaml b/fast/stages/2-networking-b-vpn/data/firewall-rules/landing/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/firewall-rules/landing/rules.yaml
rename to fast/stages/2-networking-b-vpn/data/firewall-rules/landing/rules.yaml
diff --git a/fast/stages/02-networking-peering/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-b-vpn/data/hierarchical-policy-rules.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/hierarchical-policy-rules.yaml
rename to fast/stages/2-networking-b-vpn/data/hierarchical-policy-rules.yaml
diff --git a/fast/stages/02-networking-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml
rename to fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml
diff --git a/fast/stages/02-networking-separate-envs/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/subnets/dev/dev-default-ew1.yaml
rename to fast/stages/2-networking-b-vpn/data/subnets/dev/dev-default-ew1.yaml
diff --git a/fast/stages/02-networking-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml
rename to fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml
diff --git a/fast/stages/02-networking-vpn/data/subnets/landing/landing-default-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/landing/landing-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/subnets/landing/landing-default-ew1.yaml
rename to fast/stages/2-networking-b-vpn/data/subnets/landing/landing-default-ew1.yaml
diff --git a/fast/stages/02-networking-separate-envs/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/prod/prod-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/subnets/prod/prod-default-ew1.yaml
rename to fast/stages/2-networking-b-vpn/data/subnets/prod/prod-default-ew1.yaml
diff --git a/fast/stages/02-networking-vpn/diagram.png b/fast/stages/2-networking-b-vpn/diagram.png
similarity index 100%
rename from fast/stages/02-networking-vpn/diagram.png
rename to fast/stages/2-networking-b-vpn/diagram.png
diff --git a/fast/stages/02-networking-vpn/diagram.svg b/fast/stages/2-networking-b-vpn/diagram.svg
similarity index 100%
rename from fast/stages/02-networking-vpn/diagram.svg
rename to fast/stages/2-networking-b-vpn/diagram.svg
diff --git a/fast/stages/02-networking-vpn/dns-dev.tf b/fast/stages/2-networking-b-vpn/dns-dev.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/dns-dev.tf
rename to fast/stages/2-networking-b-vpn/dns-dev.tf
diff --git a/fast/stages/02-networking-vpn/dns-landing.tf b/fast/stages/2-networking-b-vpn/dns-landing.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/dns-landing.tf
rename to fast/stages/2-networking-b-vpn/dns-landing.tf
diff --git a/fast/stages/02-networking-vpn/dns-prod.tf b/fast/stages/2-networking-b-vpn/dns-prod.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/dns-prod.tf
rename to fast/stages/2-networking-b-vpn/dns-prod.tf
diff --git a/fast/stages/02-networking-vpn/landing.tf b/fast/stages/2-networking-b-vpn/landing.tf
similarity index 82%
rename from fast/stages/02-networking-vpn/landing.tf
rename to fast/stages/2-networking-b-vpn/landing.tf
index 83a0d509a..37e3adfd4 100644
--- a/fast/stages/02-networking-vpn/landing.tf
+++ b/fast/stages/2-networking-b-vpn/landing.tf
@@ -63,7 +63,7 @@ module "landing-vpc" {
next_hop = "default-internet-gateway"
}
}
- data_folder = "${var.data_dir}/subnets/landing"
+ data_folder = "${var.factories_config.data_dir}/subnets/landing"
}
module "landing-firewall" {
@@ -74,18 +74,23 @@ module "landing-firewall" {
disabled = true
}
factories_config = {
- cidr_tpl_file = "${var.data_dir}/cidrs.yaml"
- rules_folder = "${var.data_dir}/firewall-rules/landing"
+ cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ rules_folder = "${var.factories_config.data_dir}/firewall-rules/landing"
}
}
-module "landing-nat-ew1" {
+moved {
+ from = module.landing-nat-ew1
+ to = module.landing-nat-primary
+}
+
+module "landing-nat-primary" {
source = "../../../modules/net-cloudnat"
project_id = module.landing-project.project_id
- region = "europe-west1"
- name = "ew1"
+ region = var.regions.primary
+ name = local.region_shortnames[var.regions.primary]
router_create = true
- router_name = "prod-nat-ew1"
+ router_name = "prod-nat-${local.region_shortnames[var.regions.primary]}"
router_network = module.landing-vpc.name
router_asn = 4200001024
}
diff --git a/fast/stages/02-networking-peering/main.tf b/fast/stages/2-networking-b-vpn/main.tf
similarity index 76%
rename from fast/stages/02-networking-peering/main.tf
rename to fast/stages/2-networking-b-vpn/main.tf
index f68d39eb8..5888752d7 100644
--- a/fast/stages/02-networking-peering/main.tf
+++ b/fast/stages/2-networking-b-vpn/main.tf
@@ -17,14 +17,14 @@
# tfdoc:file:description Networking folder and hierarchical policy.
locals {
+ # combine all regions from variables and subnets
+ regions = distinct(concat(
+ values(var.regions),
+ values(module.dev-spoke-vpc.subnet_regions),
+ values(module.landing-vpc.subnet_regions),
+ values(module.prod-spoke-vpc.subnet_regions),
+ ))
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}"
- })]
- }
stage3_sas_delegated_grants = [
"roles/composer.sharedVpcAgent",
"roles/compute.networkUser",
@@ -46,9 +46,9 @@ module "folder" {
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"
+ cidr_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ policy_name = var.factories_config.firewall_policy_name
+ rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml"
}
firewall_policy_association = {
factory-policy = "factory"
diff --git a/fast/stages/02-networking-nva/monitoring.tf b/fast/stages/2-networking-b-vpn/monitoring.tf
similarity index 93%
rename from fast/stages/02-networking-nva/monitoring.tf
rename to fast/stages/2-networking-b-vpn/monitoring.tf
index 7b8b70c51..be3a47faa 100644
--- a/fast/stages/02-networking-nva/monitoring.tf
+++ b/fast/stages/2-networking-b-vpn/monitoring.tf
@@ -17,7 +17,7 @@
# tfdoc:file:description Network monitoring dashboards.
locals {
- dashboard_path = "${var.data_dir}/dashboards"
+ dashboard_path = "${var.factories_config.data_dir}/dashboards"
dashboard_files = fileset(local.dashboard_path, "*.json")
dashboards = {
for filename in local.dashboard_files :
diff --git a/fast/stages/02-networking-vpn/outputs.tf b/fast/stages/2-networking-b-vpn/outputs.tf
similarity index 93%
rename from fast/stages/02-networking-vpn/outputs.tf
rename to fast/stages/2-networking-b-vpn/outputs.tf
index 3b97b7f25..628c706b3 100644
--- a/fast/stages/02-networking-vpn/outputs.tf
+++ b/fast/stages/2-networking-b-vpn/outputs.tf
@@ -48,13 +48,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/02-networking.auto.tfvars.json"
+ filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/02-networking.auto.tfvars.json"
+ name = "tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
@@ -89,8 +89,8 @@ output "tfvars" {
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 :
+ onprem-primary = {
+ for v in module.landing-to-onprem-primary-vpn[0].gateway.vpn_interfaces :
v.id => v.ip_address
}
}
diff --git a/fast/stages/2-networking-b-vpn/regions.tf b/fast/stages/2-networking-b-vpn/regions.tf
new file mode 100644
index 000000000..53514afa9
--- /dev/null
+++ b/fast/stages/2-networking-b-vpn/regions.tf
@@ -0,0 +1,42 @@
+/**
+ * Copyright 2023 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 Compute short names for regions.
+
+locals {
+ # only map when the first character would not work
+ _region_cardinal = {
+ southeast = "se"
+ }
+ # only map when the first character would not work
+ _region_geo = {
+ australia = "o"
+ }
+ # split in [geo, cardinal, number] tokens
+ _region_tokens = {
+ for v in local.regions : v => regexall("(?:[a-z]+)|(?:[0-9]+)", v)
+ }
+ region_shortnames = {
+ for k, v in local._region_tokens : k => join("", [
+ # first token via geo alias map or first character
+ lookup(local._region_geo, v.0, substr(v.0, 0, 1)),
+ # first token via cardinal alias map or first character
+ lookup(local._region_cardinal, v.1, substr(v.1, 0, 1)),
+ # region number as is
+ v.2
+ ])
+ }
+}
diff --git a/fast/stages/02-networking-vpn/spoke-dev.tf b/fast/stages/2-networking-b-vpn/spoke-dev.tf
similarity index 84%
rename from fast/stages/02-networking-vpn/spoke-dev.tf
rename to fast/stages/2-networking-b-vpn/spoke-dev.tf
index e67cfb70d..ed7d13cb0 100644
--- a/fast/stages/02-networking-vpn/spoke-dev.tf
+++ b/fast/stages/2-networking-b-vpn/spoke-dev.tf
@@ -16,6 +16,19 @@
# tfdoc:file:description Dev spoke VPC and related resources.
+locals {
+ _l7ilb_subnets_dev = [
+ for v in var.l7ilb_subnets.dev : merge(v, {
+ active = true
+ region = lookup(var.regions, v.region, v.region)
+ })]
+ l7ilb_subnets_dev = [
+ for v in local._l7ilb_subnets_dev : merge(v, {
+ name = "dev-l7ilb-${local.region_shortnames[v.region]}"
+ })
+ ]
+}
+
module "dev-spoke-project" {
source = "../../../modules/project"
billing_account = var.billing_account.id
@@ -48,9 +61,9 @@ module "dev-spoke-vpc" {
project_id = module.dev-spoke-project.project_id
name = "dev-spoke-0"
mtu = 1500
- data_folder = "${var.data_dir}/subnets/dev"
+ data_folder = "${var.factories_config.data_dir}/subnets/dev"
psa_config = try(var.psa_ranges.dev, null)
- subnets_proxy_only = local.l7ilb_subnets.dev
+ subnets_proxy_only = local.l7ilb_subnets_dev
# set explicit routes for googleapis in case the default route is deleted
routes = {
private-googleapis = {
@@ -74,8 +87,8 @@ module "dev-spoke-firewall" {
disabled = true
}
factories_config = {
- cidr_tpl_file = "${var.data_dir}/cidrs.yaml"
- rules_folder = "${var.data_dir}/firewall-rules/dev"
+ cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ rules_folder = "${var.factories_config.data_dir}/firewall-rules/dev"
}
}
@@ -84,7 +97,7 @@ module "dev-spoke-cloudnat" {
source = "../../../modules/net-cloudnat"
project_id = module.dev-spoke-project.project_id
region = each.value
- name = "dev-nat-${var.region_trigram[each.value]}"
+ name = "dev-nat-${local.region_shortnames[each.value]}"
router_create = true
router_network = module.dev-spoke-vpc.name
router_asn = 4200001024
diff --git a/fast/stages/02-networking-peering/spoke-prod.tf b/fast/stages/2-networking-b-vpn/spoke-prod.tf
similarity index 84%
rename from fast/stages/02-networking-peering/spoke-prod.tf
rename to fast/stages/2-networking-b-vpn/spoke-prod.tf
index cf49152fa..f584b32da 100644
--- a/fast/stages/02-networking-peering/spoke-prod.tf
+++ b/fast/stages/2-networking-b-vpn/spoke-prod.tf
@@ -16,6 +16,19 @@
# tfdoc:file:description Production spoke VPC and related resources.
+locals {
+ _l7ilb_subnets_prod = [
+ for v in var.l7ilb_subnets.prod : merge(v, {
+ active = true
+ region = lookup(var.regions, v.region, v.region)
+ })]
+ l7ilb_subnets_prod = [
+ for v in local._l7ilb_subnets_prod : merge(v, {
+ name = "prod-l7ilb-${local.region_shortnames[v.region]}"
+ })
+ ]
+}
+
module "prod-spoke-project" {
source = "../../../modules/project"
billing_account = var.billing_account.id
@@ -48,9 +61,9 @@ module "prod-spoke-vpc" {
project_id = module.prod-spoke-project.project_id
name = "prod-spoke-0"
mtu = 1500
- data_folder = "${var.data_dir}/subnets/prod"
+ data_folder = "${var.factories_config.data_dir}/subnets/prod"
psa_config = try(var.psa_ranges.prod, null)
- subnets_proxy_only = local.l7ilb_subnets.prod
+ subnets_proxy_only = local.l7ilb_subnets_prod
# set explicit routes for googleapis in case the default route is deleted
routes = {
private-googleapis = {
@@ -74,8 +87,8 @@ module "prod-spoke-firewall" {
disabled = true
}
factories_config = {
- cidr_tpl_file = "${var.data_dir}/cidrs.yaml"
- rules_folder = "${var.data_dir}/firewall-rules/prod"
+ cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ rules_folder = "${var.factories_config.data_dir}/firewall-rules/prod"
}
}
@@ -84,7 +97,7 @@ module "prod-spoke-cloudnat" {
source = "../../../modules/net-cloudnat"
project_id = module.prod-spoke-project.project_id
region = each.value
- name = "prod-nat-${var.region_trigram[each.value]}"
+ name = "prod-nat-${local.region_shortnames[each.value]}"
router_create = true
router_network = module.prod-spoke-vpc.name
router_asn = 4200001024
diff --git a/fast/stages/02-networking-vpn/test-resources.tf b/fast/stages/2-networking-b-vpn/test-resources.tf
similarity index 76%
rename from fast/stages/02-networking-vpn/test-resources.tf
rename to fast/stages/2-networking-b-vpn/test-resources.tf
index 204971fec..67073845d 100644
--- a/fast/stages/02-networking-vpn/test-resources.tf
+++ b/fast/stages/2-networking-b-vpn/test-resources.tf
@@ -19,11 +19,11 @@
# module "test-vm-landing-0" {
# source = "../../../modules/compute-vm"
# project_id = module.landing-project.project_id
-# zone = "europe-west1-b"
+# zone = "${var.regions.primary}-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"]
+# subnetwork = module.landing-vpc.subnet_self_links["${var.regions.primary}/landing-default-${local.region_shortnames[var.regions.primary]}"]
# }]
# tags = ["ssh"]
# service_account_create = true
@@ -31,8 +31,8 @@
# image = "projects/debian-cloud/global/images/family/debian-10"
# }
# options = {
-# spot = true
-# termination_action = "STOP"
+# spot = true
+# termination_action = "STOP"
# }
# metadata = {
# startup-script = <google_monitoring_dashboard |
| [nva.tf](./nva.tf) | None | compute-mig · compute-vm · simple-nva | |
| [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file |
+| [regions.tf](./regions.tf) | Compute short names for regions. | | |
| [spoke-dev.tf](./spoke-dev.tf) | Dev spoke VPC and related resources. | net-vpc · net-vpc-firewall · net-vpc-peering · project | google_project_iam_binding |
| [spoke-prod.tf](./spoke-prod.tf) | Production spoke VPC and related resources. | net-vpc · net-vpc-firewall · net-vpc-peering · project | google_project_iam_binding |
| [test-resources.tf](./test-resources.tf) | temporary instances for testing | compute-vm | |
@@ -371,23 +404,23 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap |
-| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap |
-| [folder_ids](variables.tf#L79) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 01-resman |
-| [organization](variables.tf#L115) | Organization details. | object({…}) | ✓ | | 00-bootstrap |
-| [prefix](variables.tf#L131) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap |
-| [custom_adv](variables.tf#L34) | Custom advertisement definitions in name => range format. | map(string) | | {…} | |
-| [custom_roles](variables.tf#L56) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 00-bootstrap |
-| [data_dir](variables.tf#L65) | Relative path for the folder storing configuration data for network resources. | string | | "data" | |
-| [dns](variables.tf#L71) | Onprem DNS resolvers. | map(list(string)) | | {…} | |
-| [l7ilb_subnets](variables.tf#L89) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | |
-| [onprem_cidr](variables.tf#L107) | Onprem addresses in name => range format. | map(string) | | {…} | |
-| [outputs_location](variables.tf#L125) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | |
-| [psa_ranges](variables.tf#L142) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | |
-| [region_trigram](variables.tf#L183) | Short names for GCP regions. | map(string) | | {…} | |
-| [router_configs](variables.tf#L192) | Configurations for CRs and onprem routers. | map(object({…})) | | {…} | |
-| [service_accounts](variables.tf#L215) | Automation service accounts in name => email format. | object({…}) | | null | 01-resman |
-| [vpn_onprem_configs](variables.tf#L229) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | |
+| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap |
+| [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap |
+| [folder_ids](variables.tf#L97) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 1-resman |
+| [organization](variables.tf#L133) | Organization details. | object({…}) | ✓ | | 0-bootstrap |
+| [prefix](variables.tf#L149) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap |
+| [custom_adv](variables.tf#L38) | Custom advertisement definitions in name => range format. | map(string) | | {…} | |
+| [custom_roles](variables.tf#L60) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap |
+| [dns](variables.tf#L69) | Onprem DNS resolvers. | map(list(string)) | | {…} | |
+| [factories_config](variables.tf#L77) | Configuration for network resource factories. | object({…}) | | {…} | |
+| [l7ilb_subnets](variables.tf#L107) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | |
+| [onprem_cidr](variables.tf#L125) | Onprem addresses in name => range format. | map(string) | | {…} | |
+| [outputs_location](variables.tf#L143) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | |
+| [psa_ranges](variables.tf#L160) | IP ranges used for Private Service Access (e.g. CloudSQL). Ranges is in name => range format. | object({…}) | | null | |
+| [regions](variables.tf#L181) | Region definitions. | object({…}) | | {…} | |
+| [router_configs](variables.tf#L193) | Configurations for CRs and onprem routers. | map(object({…})) | | {…} | |
+| [service_accounts](variables.tf#L216) | Automation service accounts in name => email format. | object({…}) | | null | 1-resman |
+| [vpn_onprem_configs](variables.tf#L230) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | |
## Outputs
diff --git a/fast/stages/02-networking-separate-envs/data/cidrs.yaml b/fast/stages/2-networking-c-nva/data/cidrs.yaml
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/cidrs.yaml
rename to fast/stages/2-networking-c-nva/data/cidrs.yaml
diff --git a/fast/stages/02-networking-nva/data/dashboards/firewall_insights.json b/fast/stages/2-networking-c-nva/data/dashboards/firewall_insights.json
similarity index 100%
rename from fast/stages/02-networking-nva/data/dashboards/firewall_insights.json
rename to fast/stages/2-networking-c-nva/data/dashboards/firewall_insights.json
diff --git a/fast/stages/02-networking-nva/data/dashboards/vpn.json b/fast/stages/2-networking-c-nva/data/dashboards/vpn.json
similarity index 100%
rename from fast/stages/02-networking-nva/data/dashboards/vpn.json
rename to fast/stages/2-networking-c-nva/data/dashboards/vpn.json
diff --git a/fast/stages/02-networking-vpn/data/firewall-rules/dev/rules.yaml b/fast/stages/2-networking-c-nva/data/firewall-rules/dev/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/firewall-rules/dev/rules.yaml
rename to fast/stages/2-networking-c-nva/data/firewall-rules/dev/rules.yaml
diff --git a/fast/stages/02-networking-nva/data/firewall-rules/landing-trusted/rules.yaml b/fast/stages/2-networking-c-nva/data/firewall-rules/landing-trusted/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/firewall-rules/landing-trusted/rules.yaml
rename to fast/stages/2-networking-c-nva/data/firewall-rules/landing-trusted/rules.yaml
diff --git a/fast/stages/02-networking-nva/data/firewall-rules/landing-untrusted/rules.yaml b/fast/stages/2-networking-c-nva/data/firewall-rules/landing-untrusted/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/firewall-rules/landing-untrusted/rules.yaml
rename to fast/stages/2-networking-c-nva/data/firewall-rules/landing-untrusted/rules.yaml
diff --git a/fast/stages/02-networking-separate-envs/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-c-nva/data/hierarchical-policy-rules.yaml
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/hierarchical-policy-rules.yaml
rename to fast/stages/2-networking-c-nva/data/hierarchical-policy-rules.yaml
diff --git a/fast/stages/02-networking-nva/data/nva-startup-script.tftpl b/fast/stages/2-networking-c-nva/data/nva-startup-script.tftpl
similarity index 100%
rename from fast/stages/02-networking-nva/data/nva-startup-script.tftpl
rename to fast/stages/2-networking-c-nva/data/nva-startup-script.tftpl
diff --git a/fast/stages/02-networking-nva/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/dev/dev-dataplatform-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/dev/dev-dataplatform-ew1.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/dev/dev-dataplatform-ew1.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/dev/dev-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew1.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/dev/dev-default-ew1.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew4.yaml b/fast/stages/2-networking-c-nva/data/subnets/dev/dev-default-ew4.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew4.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/dev/dev-default-ew4.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml b/fast/stages/2-networking-c-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml b/fast/stages/2-networking-c-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/prod/prod-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew1.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/prod/prod-default-ew1.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew4.yaml b/fast/stages/2-networking-c-nva/data/subnets/prod/prod-default-ew4.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew4.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/prod/prod-default-ew4.yaml
diff --git a/fast/stages/02-networking-nva/diagram.png b/fast/stages/2-networking-c-nva/diagram.png
similarity index 100%
rename from fast/stages/02-networking-nva/diagram.png
rename to fast/stages/2-networking-c-nva/diagram.png
diff --git a/fast/stages/02-networking-nva/diagram.svg b/fast/stages/2-networking-c-nva/diagram.svg
similarity index 100%
rename from fast/stages/02-networking-nva/diagram.svg
rename to fast/stages/2-networking-c-nva/diagram.svg
diff --git a/fast/stages/02-networking-nva/dns-dev.tf b/fast/stages/2-networking-c-nva/dns-dev.tf
similarity index 100%
rename from fast/stages/02-networking-nva/dns-dev.tf
rename to fast/stages/2-networking-c-nva/dns-dev.tf
diff --git a/fast/stages/02-networking-nva/dns-landing.tf b/fast/stages/2-networking-c-nva/dns-landing.tf
similarity index 100%
rename from fast/stages/02-networking-nva/dns-landing.tf
rename to fast/stages/2-networking-c-nva/dns-landing.tf
diff --git a/fast/stages/02-networking-nva/dns-prod.tf b/fast/stages/2-networking-c-nva/dns-prod.tf
similarity index 100%
rename from fast/stages/02-networking-nva/dns-prod.tf
rename to fast/stages/2-networking-c-nva/dns-prod.tf
diff --git a/fast/stages/02-networking-nva/landing.tf b/fast/stages/2-networking-c-nva/landing.tf
similarity index 76%
rename from fast/stages/02-networking-nva/landing.tf
rename to fast/stages/2-networking-c-nva/landing.tf
index 5a990030f..e66b03db9 100644
--- a/fast/stages/02-networking-nva/landing.tf
+++ b/fast/stages/2-networking-c-nva/landing.tf
@@ -53,7 +53,7 @@ module "landing-untrusted-vpc" {
inbound = false
logging = false
}
- data_folder = "${var.data_dir}/subnets/landing-untrusted"
+ data_folder = "${var.factories_config.data_dir}/subnets/landing-untrusted"
}
module "landing-untrusted-firewall" {
@@ -64,31 +64,41 @@ module "landing-untrusted-firewall" {
disabled = true
}
factories_config = {
- cidr_tpl_file = "${var.data_dir}/cidrs.yaml"
- rules_folder = "${var.data_dir}/firewall-rules/landing-untrusted"
+ cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ rules_folder = "${var.factories_config.data_dir}/firewall-rules/landing-untrusted"
}
}
# NAT
-module "landing-nat-ew1" {
+moved {
+ from = module.landing-nat-ew1
+ to = module.landing-nat-primary
+}
+
+module "landing-nat-primary" {
source = "../../../modules/net-cloudnat"
project_id = module.landing-project.project_id
- region = "europe-west1"
- name = "ew1"
+ region = var.regions.primary
+ name = local.region_shortnames[var.regions.primary]
router_create = true
- router_name = "prod-nat-ew1"
+ router_name = "prod-nat-${local.region_shortnames[var.regions.primary]}"
router_network = module.landing-untrusted-vpc.name
router_asn = 4200001024
}
-module "landing-nat-ew4" {
+moved {
+ from = module.landing-nat-ew4
+ to = module.landing-nat-secondary
+}
+
+module "landing-nat-secondary" {
source = "../../../modules/net-cloudnat"
project_id = module.landing-project.project_id
- region = "europe-west4"
- name = "ew4"
+ region = var.regions.secondary
+ name = local.region_shortnames[var.regions.secondary]
router_create = true
- router_name = "prod-nat-ew4"
+ router_name = "prod-nat-${local.region_shortnames[var.regions.secondary]}"
router_network = module.landing-untrusted-vpc.name
router_asn = 4200001024
}
@@ -101,7 +111,10 @@ module "landing-trusted-vpc" {
name = "prod-trusted-landing-0"
delete_default_routes_on_create = true
mtu = 1500
-
+ data_folder = "${var.factories_config.data_dir}/subnets/landing-trusted"
+ dns_policy = {
+ inbound = true
+ }
# Set explicit routes for googleapis in case the default route is deleted
routes = {
private-googleapis = {
@@ -115,12 +128,6 @@ module "landing-trusted-vpc" {
next_hop = "default-internet-gateway"
}
}
-
- dns_policy = {
- inbound = true
- }
-
- data_folder = "${var.data_dir}/subnets/landing-trusted"
}
module "landing-trusted-firewall" {
@@ -131,7 +138,7 @@ module "landing-trusted-firewall" {
disabled = true
}
factories_config = {
- cidr_tpl_file = "${var.data_dir}/cidrs.yaml"
- rules_folder = "${var.data_dir}/firewall-rules/landing-trusted"
+ cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ rules_folder = "${var.factories_config.data_dir}/firewall-rules/landing-trusted"
}
}
diff --git a/fast/stages/02-networking-nva/main.tf b/fast/stages/2-networking-c-nva/main.tf
similarity index 73%
rename from fast/stages/02-networking-nva/main.tf
rename to fast/stages/2-networking-c-nva/main.tf
index 4db5061ba..e4066ba35 100644
--- a/fast/stages/02-networking-nva/main.tf
+++ b/fast/stages/2-networking-c-nva/main.tf
@@ -24,6 +24,14 @@ locals {
name = "${env}-l7ilb-${s.region}"
})]
}
+ # combine all regions from variables and subnets
+ regions = distinct(concat(
+ values(var.regions),
+ values(module.dev-spoke-vpc.subnet_regions),
+ values(module.landing-trusted-vpc.subnet_regions),
+ values(module.landing-untrusted-vpc.subnet_regions),
+ values(module.prod-spoke-vpc.subnet_regions),
+ ))
service_accounts = {
for k, v in coalesce(var.service_accounts, {}) :
k => "serviceAccount:${v}" if v != null
@@ -45,11 +53,11 @@ module "folder" {
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"
+ cidr_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ policy_name = var.factories_config.firewall_policy_name
+ rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml"
}
firewall_policy_association = {
- factory-policy = "factory"
+ factory-policy = var.factories_config.firewall_policy_name
}
}
diff --git a/fast/stages/02-networking-peering/monitoring.tf b/fast/stages/2-networking-c-nva/monitoring.tf
similarity index 93%
rename from fast/stages/02-networking-peering/monitoring.tf
rename to fast/stages/2-networking-c-nva/monitoring.tf
index 7b8b70c51..be3a47faa 100644
--- a/fast/stages/02-networking-peering/monitoring.tf
+++ b/fast/stages/2-networking-c-nva/monitoring.tf
@@ -17,7 +17,7 @@
# tfdoc:file:description Network monitoring dashboards.
locals {
- dashboard_path = "${var.data_dir}/dashboards"
+ dashboard_path = "${var.factories_config.data_dir}/dashboards"
dashboard_files = fileset(local.dashboard_path, "*.json")
dashboards = {
for filename in local.dashboard_files :
diff --git a/fast/stages/02-networking-nva/nva.tf b/fast/stages/2-networking-c-nva/nva.tf
similarity index 66%
rename from fast/stages/02-networking-nva/nva.tf
rename to fast/stages/2-networking-c-nva/nva.tf
index f4f7b9e5e..7ae9c30c8 100644
--- a/fast/stages/02-networking-nva/nva.tf
+++ b/fast/stages/2-networking-c-nva/nva.tf
@@ -21,29 +21,32 @@ locals {
{
name = "untrusted"
routes = [
- var.custom_adv.gcp_landing_untrusted_ew1,
- var.custom_adv.gcp_landing_untrusted_ew4,
+ var.custom_adv.gcp_landing_untrusted_primary,
+ var.custom_adv.gcp_landing_untrusted_secondary,
]
},
{
name = "trusted"
routes = [
- var.custom_adv.gcp_dev_ew1,
- var.custom_adv.gcp_dev_ew4,
- var.custom_adv.gcp_landing_trusted_ew1,
- var.custom_adv.gcp_landing_trusted_ew4,
- var.custom_adv.gcp_prod_ew1,
- var.custom_adv.gcp_prod_ew4,
+ var.custom_adv.gcp_dev_primary,
+ var.custom_adv.gcp_dev_secondary,
+ var.custom_adv.gcp_landing_trusted_primary,
+ var.custom_adv.gcp_landing_trusted_secondary,
+ var.custom_adv.gcp_prod_primary,
+ var.custom_adv.gcp_prod_secondary,
]
},
]
nva_locality = {
- europe-west1-b = { region = "europe-west1", trigram = "ew1", zone = "b" },
- europe-west1-c = { region = "europe-west1", trigram = "ew1", zone = "c" },
- europe-west4-b = { region = "europe-west4", trigram = "ew4", zone = "b" },
- europe-west4-c = { region = "europe-west4", trigram = "ew4", zone = "c" },
+ for v in setproduct(keys(var.regions), local.nva_zones) :
+ join("-", v) => {
+ name = v.0
+ region = var.regions[v.0]
+ shortname = local.region_shortnames[var.regions[v.0]]
+ zone = v.1
+ }
}
-
+ nva_zones = ["b", "c"]
}
# NVA config
@@ -57,7 +60,7 @@ module "nva-template" {
for_each = local.nva_locality
source = "../../../modules/compute-vm"
project_id = module.landing-project.project_id
- name = "nva-template-${each.value.trigram}-${each.value.zone}"
+ name = "nva-template-${each.key}"
zone = "${each.value.region}-${each.value.zone}"
instance_type = "e2-standard-2"
tags = ["nva"]
@@ -66,13 +69,13 @@ module "nva-template" {
network_interfaces = [
{
network = module.landing-untrusted-vpc.self_link
- subnetwork = module.landing-untrusted-vpc.subnet_self_links["${each.value.region}/landing-untrusted-default-${each.value.trigram}"]
+ subnetwork = module.landing-untrusted-vpc.subnet_self_links["${each.value.region}/landing-untrusted-default-${each.value.shortname}"]
nat = false
addresses = null
},
{
network = module.landing-trusted-vpc.self_link
- subnetwork = module.landing-trusted-vpc.subnet_self_links["${each.value.region}/landing-trusted-default-${each.value.trigram}"]
+ subnetwork = module.landing-trusted-vpc.subnet_self_links["${each.value.region}/landing-trusted-default-${each.value.shortname}"]
nat = false
addresses = null
}
@@ -98,7 +101,7 @@ module "nva-mig" {
source = "../../../modules/compute-mig"
project_id = module.landing-project.project_id
location = each.value.region
- name = "nva-cos-${each.value.trigram}-${each.value.zone}"
+ name = "nva-cos-${each.key}"
instance_template = module.nva-template[each.key].template.self_link
target_size = 1
auto_healing_policies = {
@@ -113,21 +116,27 @@ module "nva-mig" {
}
module "ilb-nva-untrusted" {
- for_each = { for l in local.nva_locality : l.region => l.trigram... }
+ for_each = {
+ for k, v in var.regions : k => {
+ region = v
+ shortname = local.region_shortnames[v]
+ subnet = "${v}/landing-untrusted-default-${local.region_shortnames[v]}"
+ }
+ }
source = "../../../modules/net-ilb"
project_id = module.landing-project.project_id
- region = each.key
- name = "nva-untrusted-${each.value.0}"
+ region = each.value.region
+ name = "nva-untrusted-${each.key}"
service_label = var.prefix
global_access = true
vpc_config = {
network = module.landing-untrusted-vpc.self_link
- subnetwork = module.landing-untrusted-vpc.subnet_self_links["${each.key}/landing-untrusted-default-${each.value.0}"]
+ subnetwork = module.landing-untrusted-vpc.subnet_self_links[each.value.subnet]
}
backends = [
- for key, _ in local.nva_locality : {
- group = module.nva-mig[key].group_manager.instance_group
- } if local.nva_locality[key].region == each.key
+ for k, v in module.nva-mig :
+ { group = v.group_manager.instance_group }
+ if startswith(k, each.key)
]
health_check_config = {
enable_logging = true
@@ -137,23 +146,28 @@ module "ilb-nva-untrusted" {
}
}
-
module "ilb-nva-trusted" {
- for_each = { for l in local.nva_locality : l.region => l.trigram... }
+ for_each = {
+ for k, v in var.regions : k => {
+ region = v
+ shortname = local.region_shortnames[v]
+ subnet = "${v}/landing-trusted-default-${local.region_shortnames[v]}"
+ }
+ }
source = "../../../modules/net-ilb"
project_id = module.landing-project.project_id
- region = each.key
- name = "nva-trusted-${each.value.0}"
+ region = each.value.region
+ name = "nva-trusted-${each.key}"
service_label = var.prefix
global_access = true
vpc_config = {
network = module.landing-trusted-vpc.self_link
- subnetwork = module.landing-trusted-vpc.subnet_self_links["${each.key}/landing-trusted-default-${each.value.0}"]
+ subnetwork = module.landing-trusted-vpc.subnet_self_links[each.value.subnet]
}
backends = [
- for key, _ in local.nva_locality : {
- group = module.nva-mig[key].group_manager.instance_group
- } if local.nva_locality[key].region == each.key
+ for k, v in module.nva-mig :
+ { group = v.group_manager.instance_group }
+ if startswith(k, each.key)
]
health_check_config = {
enable_logging = true
@@ -162,4 +176,3 @@ module "ilb-nva-trusted" {
}
}
}
-
diff --git a/fast/stages/02-networking-nva/outputs.tf b/fast/stages/2-networking-c-nva/outputs.tf
similarity index 89%
rename from fast/stages/02-networking-nva/outputs.tf
rename to fast/stages/2-networking-c-nva/outputs.tf
index df324570d..746202427 100644
--- a/fast/stages/02-networking-nva/outputs.tf
+++ b/fast/stages/2-networking-c-nva/outputs.tf
@@ -43,13 +43,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/02-networking.auto.tfvars.json"
+ filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/02-networking.auto.tfvars.json"
+ name = "tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
@@ -79,12 +79,12 @@ output "tfvars" {
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 :
+ onprem-primary = {
+ for v in module.landing-to-onprem-primary-vpn[0].gateway.vpn_interfaces :
v.id => v.ip_address
}
- onprem-ew4 = {
- for v in module.landing-to-onprem-ew4-vpn[0].gateway.vpn_interfaces :
+ onprem-secondary = {
+ for v in module.landing-to-onprem-secondary-vpn[0].gateway.vpn_interfaces :
v.id => v.ip_address
}
}
diff --git a/fast/stages/2-networking-c-nva/regions.tf b/fast/stages/2-networking-c-nva/regions.tf
new file mode 100644
index 000000000..53514afa9
--- /dev/null
+++ b/fast/stages/2-networking-c-nva/regions.tf
@@ -0,0 +1,42 @@
+/**
+ * Copyright 2023 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 Compute short names for regions.
+
+locals {
+ # only map when the first character would not work
+ _region_cardinal = {
+ southeast = "se"
+ }
+ # only map when the first character would not work
+ _region_geo = {
+ australia = "o"
+ }
+ # split in [geo, cardinal, number] tokens
+ _region_tokens = {
+ for v in local.regions : v => regexall("(?:[a-z]+)|(?:[0-9]+)", v)
+ }
+ region_shortnames = {
+ for k, v in local._region_tokens : k => join("", [
+ # first token via geo alias map or first character
+ lookup(local._region_geo, v.0, substr(v.0, 0, 1)),
+ # first token via cardinal alias map or first character
+ lookup(local._region_cardinal, v.1, substr(v.1, 0, 1)),
+ # region number as is
+ v.2
+ ])
+ }
+}
diff --git a/fast/stages/02-networking-nva/spoke-dev.tf b/fast/stages/2-networking-c-nva/spoke-dev.tf
similarity index 76%
rename from fast/stages/02-networking-nva/spoke-dev.tf
rename to fast/stages/2-networking-c-nva/spoke-dev.tf
index fb88384c4..b22938131 100644
--- a/fast/stages/02-networking-nva/spoke-dev.tf
+++ b/fast/stages/2-networking-c-nva/spoke-dev.tf
@@ -16,6 +16,19 @@
# tfdoc:file:description Dev spoke VPC and related resources.
+locals {
+ _l7ilb_subnets_dev = [
+ for v in var.l7ilb_subnets.dev : merge(v, {
+ active = true
+ region = lookup(var.regions, v.region, v.region)
+ })]
+ l7ilb_subnets_dev = [
+ for v in local._l7ilb_subnets_dev : merge(v, {
+ name = "dev-l7ilb-${local.region_shortnames[v.region]}"
+ })
+ ]
+}
+
module "dev-spoke-project" {
source = "../../../modules/project"
billing_account = var.billing_account.id
@@ -47,10 +60,10 @@ module "dev-spoke-vpc" {
project_id = module.dev-spoke-project.project_id
name = "dev-spoke-0"
mtu = 1500
- data_folder = "${var.data_dir}/subnets/dev"
+ data_folder = "${var.factories_config.data_dir}/subnets/dev"
delete_default_routes_on_create = true
psa_config = try(var.psa_ranges.dev, null)
- subnets_proxy_only = local.l7ilb_subnets.dev
+ subnets_proxy_only = local.l7ilb_subnets_dev
# Set explicit routes for googleapis; send everything else to NVAs
routes = {
private-googleapis = {
@@ -65,33 +78,33 @@ module "dev-spoke-vpc" {
next_hop_type = "gateway"
next_hop = "default-internet-gateway"
}
- nva-ew1-to-ew1 = {
+ nva-primary-to-primary = {
dest_range = "0.0.0.0/0"
priority = 1000
- tags = ["ew1"]
+ tags = ["primary"]
next_hop_type = "ilb"
- next_hop = module.ilb-nva-trusted["europe-west1"].forwarding_rule_address
+ next_hop = module.ilb-nva-trusted["primary"].forwarding_rule_address
}
- nva-ew4-to-ew4 = {
+ nva-secondary-to-secondary = {
dest_range = "0.0.0.0/0"
priority = 1000
- tags = ["ew4"]
+ tags = ["secondary"]
next_hop_type = "ilb"
- next_hop = module.ilb-nva-trusted["europe-west4"].forwarding_rule_address
+ next_hop = module.ilb-nva-trusted["secondary"].forwarding_rule_address
}
- nva-ew1-to-ew4 = {
+ nva-primary-to-secondary = {
dest_range = "0.0.0.0/0"
priority = 1001
- tags = ["ew1"]
+ tags = ["primary"]
next_hop_type = "ilb"
- next_hop = module.ilb-nva-trusted["europe-west4"].forwarding_rule_address
+ next_hop = module.ilb-nva-trusted["primary"].forwarding_rule_address
}
- nva-ew4-to-ew1 = {
+ nva-secondary-to-primary = {
dest_range = "0.0.0.0/0"
priority = 1001
- tags = ["ew4"]
+ tags = ["secondary"]
next_hop_type = "ilb"
- next_hop = module.ilb-nva-trusted["europe-west1"].forwarding_rule_address
+ next_hop = module.ilb-nva-trusted["secondary"].forwarding_rule_address
}
}
}
@@ -104,8 +117,8 @@ module "dev-spoke-firewall" {
disabled = true
}
factories_config = {
- cidr_tpl_file = "${var.data_dir}/cidrs.yaml"
- rules_folder = "${var.data_dir}/firewall-rules/dev"
+ cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ rules_folder = "${var.factories_config.data_dir}/firewall-rules/dev"
}
}
diff --git a/fast/stages/02-networking-nva/spoke-prod.tf b/fast/stages/2-networking-c-nva/spoke-prod.tf
similarity index 76%
rename from fast/stages/02-networking-nva/spoke-prod.tf
rename to fast/stages/2-networking-c-nva/spoke-prod.tf
index 484550aca..51ad29745 100644
--- a/fast/stages/02-networking-nva/spoke-prod.tf
+++ b/fast/stages/2-networking-c-nva/spoke-prod.tf
@@ -16,6 +16,19 @@
# tfdoc:file:description Production spoke VPC and related resources.
+locals {
+ _l7ilb_subnets_prod = [
+ for v in var.l7ilb_subnets.prod : merge(v, {
+ active = true
+ region = lookup(var.regions, v.region, v.region)
+ })]
+ l7ilb_subnets_prod = [
+ for v in local._l7ilb_subnets_prod : merge(v, {
+ name = "prod-l7ilb-${local.region_shortnames[v.region]}"
+ })
+ ]
+}
+
module "prod-spoke-project" {
source = "../../../modules/project"
billing_account = var.billing_account.id
@@ -47,10 +60,10 @@ module "prod-spoke-vpc" {
project_id = module.prod-spoke-project.project_id
name = "prod-spoke-0"
mtu = 1500
- data_folder = "${var.data_dir}/subnets/prod"
+ data_folder = "${var.factories_config.data_dir}/subnets/prod"
delete_default_routes_on_create = true
psa_config = try(var.psa_ranges.prod, null)
- subnets_proxy_only = local.l7ilb_subnets.prod
+ subnets_proxy_only = local.l7ilb_subnets_prod
# Set explicit routes for googleapis; send everything else to NVAs
routes = {
private-googleapis = {
@@ -65,33 +78,33 @@ module "prod-spoke-vpc" {
next_hop_type = "gateway"
next_hop = "default-internet-gateway"
}
- nva-ew1-to-ew1 = {
+ nva-primary-to-primary = {
dest_range = "0.0.0.0/0"
priority = 1000
- tags = ["ew1"]
+ tags = ["primary"]
next_hop_type = "ilb"
- next_hop = module.ilb-nva-trusted["europe-west1"].forwarding_rule_address
+ next_hop = module.ilb-nva-trusted["primary"].forwarding_rule_address
}
- nva-ew4-to-ew4 = {
+ nva-secondary-to-secondary = {
dest_range = "0.0.0.0/0"
priority = 1000
- tags = ["ew4"]
+ tags = ["secondary"]
next_hop_type = "ilb"
- next_hop = module.ilb-nva-trusted["europe-west4"].forwarding_rule_address
+ next_hop = module.ilb-nva-trusted["secondary"].forwarding_rule_address
}
- nva-ew1-to-ew4 = {
+ nva-primary-to-secondary = {
dest_range = "0.0.0.0/0"
priority = 1001
- tags = ["ew1"]
+ tags = ["primary"]
next_hop_type = "ilb"
- next_hop = module.ilb-nva-trusted["europe-west4"].forwarding_rule_address
+ next_hop = module.ilb-nva-trusted["secondary"].forwarding_rule_address
}
- nva-ew4-to-ew1 = {
+ nva-secondary-to-primary = {
dest_range = "0.0.0.0/0"
priority = 1001
- tags = ["ew4"]
+ tags = ["secondary"]
next_hop_type = "ilb"
- next_hop = module.ilb-nva-trusted["europe-west1"].forwarding_rule_address
+ next_hop = module.ilb-nva-trusted["primary"].forwarding_rule_address
}
}
}
@@ -104,8 +117,8 @@ module "prod-spoke-firewall" {
disabled = true
}
factories_config = {
- cidr_tpl_file = "${var.data_dir}/cidrs.yaml"
- rules_folder = "${var.data_dir}/firewall-rules/prod"
+ cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ rules_folder = "${var.factories_config.data_dir}/firewall-rules/prod"
}
}
diff --git a/fast/stages/02-networking-nva/test-resources.tf b/fast/stages/2-networking-c-nva/test-resources.tf
similarity index 63%
rename from fast/stages/02-networking-nva/test-resources.tf
rename to fast/stages/2-networking-c-nva/test-resources.tf
index d3b6109e3..14676e811 100644
--- a/fast/stages/02-networking-nva/test-resources.tf
+++ b/fast/stages/2-networking-c-nva/test-resources.tf
@@ -18,23 +18,23 @@
# # Untrusted (Landing)
-# module "test-vm-landing-untrusted-ew1-0" {
+# module "test-vm-landing-untrusted-primary-0" {
# source = "../../../modules/compute-vm"
# project_id = module.landing-project.project_id
-# zone = "europe-west1-b"
-# name = "test-vm-lnd-unt-ew1-0"
+# zone = "${var.regions.primary}-b"
+# name = "test-vm-lnd-unt-primary-0"
# network_interfaces = [{
# network = module.landing-untrusted-vpc.self_link
-# subnetwork = module.landing-untrusted-vpc.subnet_self_links["europe-west1/landing-untrusted-default-ew1"]
+# subnetwork = module.landing-untrusted-vpc.subnet_self_links["${var.regions.primary}/landing-untrusted-default-${local.region_shortnames[var.regions.primary]}"]
# }]
-# tags = ["ew1", "ssh"]
+# tags = ["primary", "ssh"]
# service_account_create = true
# boot_disk = {
# image = "projects/debian-cloud/global/images/family/debian-10"
# }
# options = {
-# spot = true
-# termination_action = "STOP"
+# spot = true
+# termination_action = "STOP"
# }
# metadata = {
# startup-script = <folder | |
| [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard |
| [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file |
+| [regions.tf](./regions.tf) | Compute short names for regions. | | |
| [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 | |
@@ -238,21 +274,22 @@ You're now ready to run `terraform init` and `apply`.
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap |
-| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap |
-| [folder_ids](variables.tf#L74) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 01-resman |
-| [organization](variables.tf#L102) | Organization details. | object({…}) | ✓ | | 00-bootstrap |
-| [prefix](variables.tf#L118) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap |
-| [custom_adv](variables.tf#L34) | Custom advertisement definitions in name => range format. | map(string) | | {…} | |
-| [custom_roles](variables.tf#L50) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 00-bootstrap |
-| [data_dir](variables.tf#L59) | Relative path for the folder storing configuration data for network resources. | string | | "data" | |
-| [dns](variables.tf#L65) | Onprem DNS resolvers. | map(list(string)) | | {…} | |
-| [l7ilb_subnets](variables.tf#L84) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | |
-| [outputs_location](variables.tf#L112) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | |
-| [psa_ranges](variables.tf#L129) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | |
-| [router_onprem_configs](variables.tf#L166) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | |
-| [service_accounts](variables.tf#L189) | Automation service accounts in name => email format. | object({…}) | | null | 01-resman |
-| [vpn_onprem_configs](variables.tf#L201) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | |
+| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap |
+| [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap |
+| [folder_ids](variables.tf#L92) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 1-resman |
+| [organization](variables.tf#L118) | Organization details. | object({…}) | ✓ | | 0-bootstrap |
+| [prefix](variables.tf#L134) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap |
+| [custom_adv](variables.tf#L38) | Custom advertisement definitions in name => range format. | map(string) | | {…} | |
+| [custom_roles](variables.tf#L54) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap |
+| [dns](variables.tf#L63) | Onprem DNS resolvers. | map(list(string)) | | {…} | |
+| [factories_config](variables.tf#L72) | Configuration for network resource factories. | object({…}) | | {…} | |
+| [l7ilb_subnets](variables.tf#L102) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | |
+| [outputs_location](variables.tf#L128) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | |
+| [psa_ranges](variables.tf#L145) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…}) | | null | |
+| [regions](variables.tf#L182) | Region definitions. | object({…}) | | {…} | |
+| [router_onprem_configs](variables.tf#L192) | Configurations for routers used for onprem connectivity. | map(object({…})) | | {…} | |
+| [service_accounts](variables.tf#L215) | Automation service accounts in name => email format. | object({…}) | | null | 1-resman |
+| [vpn_onprem_configs](variables.tf#L227) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | |
## Outputs
diff --git a/fast/stages/02-networking-vpn/data/cidrs.yaml b/fast/stages/2-networking-d-separate-envs/data/cidrs.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/cidrs.yaml
rename to fast/stages/2-networking-d-separate-envs/data/cidrs.yaml
diff --git a/fast/stages/02-networking-vpn/data/dashboards/firewall_insights.json b/fast/stages/2-networking-d-separate-envs/data/dashboards/firewall_insights.json
similarity index 100%
rename from fast/stages/02-networking-vpn/data/dashboards/firewall_insights.json
rename to fast/stages/2-networking-d-separate-envs/data/dashboards/firewall_insights.json
diff --git a/fast/stages/02-networking-vpn/data/dashboards/vpn.json b/fast/stages/2-networking-d-separate-envs/data/dashboards/vpn.json
similarity index 100%
rename from fast/stages/02-networking-vpn/data/dashboards/vpn.json
rename to fast/stages/2-networking-d-separate-envs/data/dashboards/vpn.json
diff --git a/fast/stages/02-networking-separate-envs/data/firewall-rules/dev/rules.yaml b/fast/stages/2-networking-d-separate-envs/data/firewall-rules/dev/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/firewall-rules/dev/rules.yaml
rename to fast/stages/2-networking-d-separate-envs/data/firewall-rules/dev/rules.yaml
diff --git a/fast/stages/02-networking-vpn/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-d-separate-envs/data/hierarchical-policy-rules.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/hierarchical-policy-rules.yaml
rename to fast/stages/2-networking-d-separate-envs/data/hierarchical-policy-rules.yaml
diff --git a/fast/stages/02-networking-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml
rename to fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml
diff --git a/fast/stages/02-networking-vpn/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/subnets/dev/dev-default-ew1.yaml
rename to fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-default-ew1.yaml
diff --git a/fast/stages/02-networking-vpn/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/2-networking-d-separate-envs/data/subnets/prod/prod-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/subnets/prod/prod-default-ew1.yaml
rename to fast/stages/2-networking-d-separate-envs/data/subnets/prod/prod-default-ew1.yaml
diff --git a/fast/stages/02-networking-separate-envs/diagram.png b/fast/stages/2-networking-d-separate-envs/diagram.png
similarity index 100%
rename from fast/stages/02-networking-separate-envs/diagram.png
rename to fast/stages/2-networking-d-separate-envs/diagram.png
diff --git a/fast/stages/02-networking-separate-envs/diagram.svg b/fast/stages/2-networking-d-separate-envs/diagram.svg
similarity index 100%
rename from fast/stages/02-networking-separate-envs/diagram.svg
rename to fast/stages/2-networking-d-separate-envs/diagram.svg
diff --git a/fast/stages/02-networking-separate-envs/dns-dev.tf b/fast/stages/2-networking-d-separate-envs/dns-dev.tf
similarity index 100%
rename from fast/stages/02-networking-separate-envs/dns-dev.tf
rename to fast/stages/2-networking-d-separate-envs/dns-dev.tf
diff --git a/fast/stages/02-networking-separate-envs/dns-prod.tf b/fast/stages/2-networking-d-separate-envs/dns-prod.tf
similarity index 100%
rename from fast/stages/02-networking-separate-envs/dns-prod.tf
rename to fast/stages/2-networking-d-separate-envs/dns-prod.tf
diff --git a/fast/stages/02-networking-separate-envs/main.tf b/fast/stages/2-networking-d-separate-envs/main.tf
similarity index 65%
rename from fast/stages/02-networking-separate-envs/main.tf
rename to fast/stages/2-networking-d-separate-envs/main.tf
index 0b901e330..dda1c252e 100644
--- a/fast/stages/02-networking-separate-envs/main.tf
+++ b/fast/stages/2-networking-d-separate-envs/main.tf
@@ -18,17 +18,25 @@
locals {
custom_roles = coalesce(var.custom_roles, {})
- l7ilb_subnets = {
- for env, v in var.l7ilb_subnets : env => [
+ _l7ilb_subnets = {
+ for k, v in var.l7ilb_subnets : k => [
for s in v : merge(s, {
active = true
- name = "${env}-l7ilb-${s.region}"
+ region = lookup(var.regions, s.region, s.region)
})]
}
- region_trigram = {
- europe-west1 = "ew1"
- europe-west3 = "ew3"
+ l7ilb_subnets = {
+ for k, v in local._l7ilb_subnets : k => [
+ for s in v : merge(s, {
+ name = "${k}-l7ilb-${local.region_shortnames[s.region]}"
+ })]
}
+ # combine all regions from variables and subnets
+ regions = distinct(concat(
+ values(var.regions),
+ values(module.dev-spoke-vpc.subnet_regions),
+ values(module.prod-spoke-vpc.subnet_regions),
+ ))
stage3_sas_delegated_grants = [
"roles/composer.sharedVpcAgent",
"roles/compute.networkUser",
@@ -47,12 +55,12 @@ module "folder" {
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"
+ cidr_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ policy_name = var.factories_config.firewall_policy_name
+ rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml"
}
firewall_policy_association = {
- factory-policy = "factory"
+ factory-policy = var.factories_config.firewall_policy_name
}
}
diff --git a/fast/stages/02-networking-separate-envs/monitoring.tf b/fast/stages/2-networking-d-separate-envs/monitoring.tf
similarity index 94%
rename from fast/stages/02-networking-separate-envs/monitoring.tf
rename to fast/stages/2-networking-d-separate-envs/monitoring.tf
index 463c6a083..01ed0c479 100644
--- a/fast/stages/02-networking-separate-envs/monitoring.tf
+++ b/fast/stages/2-networking-d-separate-envs/monitoring.tf
@@ -17,7 +17,7 @@
# tfdoc:file:description Network monitoring dashboards.
locals {
- dashboard_path = "${var.data_dir}/dashboards"
+ dashboard_path = "${var.factories_config.data_dir}/dashboards"
dashboard_files = fileset(local.dashboard_path, "*.json")
dashboards = {
for filename in local.dashboard_files :
diff --git a/fast/stages/02-networking-separate-envs/outputs.tf b/fast/stages/2-networking-d-separate-envs/outputs.tf
similarity index 90%
rename from fast/stages/02-networking-separate-envs/outputs.tf
rename to fast/stages/2-networking-d-separate-envs/outputs.tf
index d06d499d6..59d70db6d 100644
--- a/fast/stages/02-networking-separate-envs/outputs.tf
+++ b/fast/stages/2-networking-d-separate-envs/outputs.tf
@@ -44,13 +44,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/02-networking.auto.tfvars.json"
+ filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/02-networking.auto.tfvars.json"
+ name = "tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
@@ -90,12 +90,12 @@ output "tfvars" {
output "vpn_gateway_endpoints" {
description = "External IP Addresses for the GCP VPN gateways."
value = local.enable_onprem_vpn == false ? null : {
- dev-onprem-ew1 = {
- for v in module.dev-to-onprem-ew1-vpn[0].gateway.vpn_interfaces :
+ dev-onprem-primary = {
+ for v in module.dev-to-onprem-primary-vpn[0].gateway.vpn_interfaces :
v.id => v.ip_address
}
- prod-onprem-ew1 = {
- for v in module.prod-to-onprem-ew1-vpn[0].gateway.vpn_interfaces :
+ prod-onprem-primary = {
+ for v in module.prod-to-onprem-primary-vpn[0].gateway.vpn_interfaces :
v.id => v.ip_address
}
}
diff --git a/fast/stages/2-networking-d-separate-envs/regions.tf b/fast/stages/2-networking-d-separate-envs/regions.tf
new file mode 100644
index 000000000..53514afa9
--- /dev/null
+++ b/fast/stages/2-networking-d-separate-envs/regions.tf
@@ -0,0 +1,42 @@
+/**
+ * Copyright 2023 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 Compute short names for regions.
+
+locals {
+ # only map when the first character would not work
+ _region_cardinal = {
+ southeast = "se"
+ }
+ # only map when the first character would not work
+ _region_geo = {
+ australia = "o"
+ }
+ # split in [geo, cardinal, number] tokens
+ _region_tokens = {
+ for v in local.regions : v => regexall("(?:[a-z]+)|(?:[0-9]+)", v)
+ }
+ region_shortnames = {
+ for k, v in local._region_tokens : k => join("", [
+ # first token via geo alias map or first character
+ lookup(local._region_geo, v.0, substr(v.0, 0, 1)),
+ # first token via cardinal alias map or first character
+ lookup(local._region_cardinal, v.1, substr(v.1, 0, 1)),
+ # region number as is
+ v.2
+ ])
+ }
+}
diff --git a/fast/stages/02-networking-separate-envs/spoke-dev.tf b/fast/stages/2-networking-d-separate-envs/spoke-dev.tf
similarity index 92%
rename from fast/stages/02-networking-separate-envs/spoke-dev.tf
rename to fast/stages/2-networking-d-separate-envs/spoke-dev.tf
index ca7d8d468..ecd0b0736 100644
--- a/fast/stages/02-networking-separate-envs/spoke-dev.tf
+++ b/fast/stages/2-networking-d-separate-envs/spoke-dev.tf
@@ -47,7 +47,7 @@ module "dev-spoke-vpc" {
project_id = module.dev-spoke-project.project_id
name = "dev-spoke-0"
mtu = 1500
- data_folder = "${var.data_dir}/subnets/dev"
+ data_folder = "${var.factories_config.data_dir}/subnets/dev"
psa_config = try(var.psa_ranges.dev, null)
subnets_proxy_only = local.l7ilb_subnets.dev
# set explicit routes for googleapis in case the default route is deleted
@@ -73,8 +73,8 @@ module "dev-spoke-firewall" {
disabled = true
}
factories_config = {
- cidr_tpl_file = "${var.data_dir}/cidrs.yaml"
- rules_folder = "${var.data_dir}/firewall-rules/dev"
+ cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ rules_folder = "${var.factories_config.data_dir}/firewall-rules/dev"
}
}
@@ -83,7 +83,7 @@ module "dev-spoke-cloudnat" {
source = "../../../modules/net-cloudnat"
project_id = module.dev-spoke-project.project_id
region = each.value
- name = "dev-nat-${local.region_trigram[each.value]}"
+ name = "dev-nat-${local.region_shortnames[each.value]}"
router_create = true
router_network = module.dev-spoke-vpc.name
router_asn = 4200001024
diff --git a/fast/stages/02-networking-separate-envs/spoke-prod.tf b/fast/stages/2-networking-d-separate-envs/spoke-prod.tf
similarity index 92%
rename from fast/stages/02-networking-separate-envs/spoke-prod.tf
rename to fast/stages/2-networking-d-separate-envs/spoke-prod.tf
index eba530a6c..2e95a09e1 100644
--- a/fast/stages/02-networking-separate-envs/spoke-prod.tf
+++ b/fast/stages/2-networking-d-separate-envs/spoke-prod.tf
@@ -47,7 +47,7 @@ module "prod-spoke-vpc" {
project_id = module.prod-spoke-project.project_id
name = "prod-spoke-0"
mtu = 1500
- data_folder = "${var.data_dir}/subnets/prod"
+ data_folder = "${var.factories_config.data_dir}/subnets/prod"
psa_config = try(var.psa_ranges.prod, null)
subnets_proxy_only = local.l7ilb_subnets.prod
# set explicit routes for googleapis in case the default route is deleted
@@ -73,8 +73,8 @@ module "prod-spoke-firewall" {
disabled = true
}
factories_config = {
- cidr_tpl_file = "${var.data_dir}/cidrs.yaml"
- rules_folder = "${var.data_dir}/firewall-rules/prod"
+ cidr_tpl_file = "${var.factories_config.data_dir}/cidrs.yaml"
+ rules_folder = "${var.factories_config.data_dir}/firewall-rules/prod"
}
}
@@ -83,7 +83,7 @@ module "prod-spoke-cloudnat" {
source = "../../../modules/net-cloudnat"
project_id = module.prod-spoke-project.project_id
region = each.value
- name = "prod-nat-${local.region_trigram[each.value]}"
+ name = "prod-nat-${local.region_shortnames[each.value]}"
router_create = true
router_network = module.prod-spoke-vpc.name
router_asn = 4200001024
diff --git a/fast/stages/02-networking-separate-envs/test-resources.tf b/fast/stages/2-networking-d-separate-envs/test-resources.tf
similarity index 86%
rename from fast/stages/02-networking-separate-envs/test-resources.tf
rename to fast/stages/2-networking-d-separate-envs/test-resources.tf
index ae3ba90a8..55c42c251 100644
--- a/fast/stages/02-networking-separate-envs/test-resources.tf
+++ b/fast/stages/2-networking-d-separate-envs/test-resources.tf
@@ -19,12 +19,12 @@
# module "test-vm-dev-0" {
# source = "../../../modules/compute-vm"
# project_id = module.dev-spoke-project.project_id
-# zone = "europe-west1-b"
+# zone = "${var.regions.primary}-b"
# name = "test-vm-0"
# network_interfaces = [{
# network = module.dev-spoke-vpc.self_link
# # change the subnet name to match the values you are actually using
-# subnetwork = module.dev-spoke-vpc.subnet_self_links["europe-west1/dev-default-ew1"]
+# subnetwork = module.dev-spoke-vpc.subnet_self_links["${var.regions.primary}/dev-default-${local.region_shortnames[var.regions.primary]}"]
# alias_ips = {}
# nat = false
# addresses = null
@@ -52,12 +52,12 @@
# module "test-vm-prod-0" {
# source = "../../../modules/compute-vm"
# project_id = module.prod-spoke-project.project_id
-# zone = "europe-west1-b"
+# zone = "${var.regions.primary}-b"
# name = "test-vm-0"
# network_interfaces = [{
# network = module.prod-spoke-vpc.self_link
# # change the subnet name to match the values you are actually using
-# subnetwork = module.prod-spoke-vpc.subnet_self_links["europe-west1/prod-default-ew1"]
+# subnetwork = module.prod-spoke-vpc.subnet_self_links["${var.regions.primary}/prod-default-${local.region_shortnames[var.regions.primary]}"]
# alias_ips = {}
# nat = false
# addresses = null
diff --git a/fast/stages/02-networking-separate-envs/variables.tf b/fast/stages/2-networking-d-separate-envs/variables.tf
similarity index 82%
rename from fast/stages/02-networking-separate-envs/variables.tf
rename to fast/stages/2-networking-d-separate-envs/variables.tf
index 019d0b2ed..03d565139 100644
--- a/fast/stages/02-networking-separate-envs/variables.tf
+++ b/fast/stages/2-networking-d-separate-envs/variables.tf
@@ -15,7 +15,7 @@
*/
variable "automation" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Automation resources created by the bootstrap stage."
type = object({
outputs_bucket = string
@@ -23,12 +23,16 @@ variable "automation" {
}
variable "billing_account" {
- # tfdoc:variable:source 00-bootstrap
- description = "Billing account id and organization id ('nnnnnnnn' or null)."
+ # tfdoc:variable:source 0-bootstrap
+ description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false."
type = object({
- id = string
- organization_id = number
+ id = string
+ is_org_level = optional(bool, true)
})
+ validation {
+ condition = var.billing_account.is_org_level != null
+ error_message = "Invalid `null` value for `billing_account.is_org_level`."
+ }
}
variable "custom_adv" {
@@ -48,7 +52,7 @@ variable "custom_adv" {
}
variable "custom_roles" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Custom roles defined at the org level, in key => id format."
type = object({
service_project_network_admin = string
@@ -56,12 +60,6 @@ variable "custom_roles" {
default = null
}
-variable "data_dir" {
- description = "Relative path for the folder storing configuration data for network resources."
- type = string
- default = "data"
-}
-
variable "dns" {
description = "Onprem DNS resolvers."
type = map(list(string))
@@ -71,8 +69,28 @@ variable "dns" {
}
}
+variable "factories_config" {
+ description = "Configuration for network resource factories."
+ type = object({
+ data_dir = optional(string, "data")
+ firewall_policy_name = optional(string, "factory")
+ })
+ default = {
+ data_dir = "data"
+ }
+ nullable = false
+ validation {
+ condition = var.factories_config.data_dir != null
+ error_message = "Data folder needs to be non-null."
+ }
+ validation {
+ condition = var.factories_config.firewall_policy_name != null
+ error_message = "Firewall policy name needs to be non-null."
+ }
+}
+
variable "folder_ids" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created."
type = object({
networking = string
@@ -90,17 +108,15 @@ variable "l7ilb_subnets" {
default = {
prod = [
{ ip_cidr_range = "10.128.92.0/24", region = "europe-west1" },
- { ip_cidr_range = "10.128.93.0/24", region = "europe-west4" }
]
dev = [
{ ip_cidr_range = "10.128.60.0/24", region = "europe-west1" },
- { ip_cidr_range = "10.128.61.0/24", region = "europe-west4" }
]
}
}
variable "organization" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Organization details."
type = object({
domain = string
@@ -116,7 +132,7 @@ variable "outputs_location" {
}
variable "prefix" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Prefix used for resources that need unique names. Use 9 characters or less."
type = string
@@ -163,6 +179,16 @@ variable "psa_ranges" {
# }
}
+variable "regions" {
+ description = "Region definitions."
+ type = object({
+ primary = string
+ })
+ default = {
+ primary = "europe-west1"
+ }
+}
+
variable "router_onprem_configs" {
description = "Configurations for routers used for onprem connectivity."
type = map(object({
@@ -173,12 +199,12 @@ variable "router_onprem_configs" {
asn = number
}))
default = {
- prod-ew1 = {
+ prod-primary = {
asn = "65533"
adv = null
# adv = { default = false, custom = [] }
}
- dev-ew1 = {
+ dev-primary = {
asn = "65534"
adv = null
# adv = { default = false, custom = [] }
@@ -187,7 +213,7 @@ variable "router_onprem_configs" {
}
variable "service_accounts" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Automation service accounts in name => email format."
type = object({
data-platform-dev = string
@@ -218,7 +244,7 @@ variable "vpn_onprem_configs" {
}))
}))
default = {
- dev-ew1 = {
+ dev-primary = {
adv = {
default = false
custom = [
@@ -247,7 +273,7 @@ variable "vpn_onprem_configs" {
}
]
}
- prod-ew1 = {
+ prod-primary = {
adv = {
default = false
custom = [
diff --git a/fast/stages/02-networking-separate-envs/vpn-onprem-dev.tf b/fast/stages/2-networking-d-separate-envs/vpn-onprem-dev.tf
similarity index 78%
rename from fast/stages/02-networking-separate-envs/vpn-onprem-dev.tf
rename to fast/stages/2-networking-d-separate-envs/vpn-onprem-dev.tf
index 1b0ff1171..e95cd14f9 100644
--- a/fast/stages/02-networking-separate-envs/vpn-onprem-dev.tf
+++ b/fast/stages/2-networking-d-separate-envs/vpn-onprem-dev.tf
@@ -32,28 +32,33 @@ locals {
}
}
-module "dev-to-onprem-ew1-vpn" {
+moved {
+ from = module.dev-to-onprem-ew1-vpn
+ to = module.dev-to-onprem-primary-vpn
+}
+
+module "dev-to-onprem-primary-vpn" {
count = local.enable_onprem_vpn ? 1 : 0
source = "../../../modules/net-vpn-ha"
project_id = module.dev-spoke-project.project_id
network = module.dev-spoke-vpc.self_link
- region = "europe-west1"
- name = "vpn-to-onprem-ew1"
+ region = var.regions.primary
+ name = "vpn-to-onprem-${local.region_shortnames[var.regions.primary]}"
router_config = {
- name = "dev-onprem-vpn-ew1"
- asn = var.router_onprem_configs.dev-ew1.asn
+ name = "dev-onprem-vpn-${local.region_shortnames[var.regions.primary]}"
+ asn = var.router_onprem_configs.dev-primary.asn
}
peer_gateway = {
- external = var.vpn_onprem_configs.dev-ew1.peer_external_gateway
+ external = var.vpn_onprem_configs.dev-primary.peer_external_gateway
}
tunnels = {
- for t in var.vpn_onprem_configs.dev-ew1.tunnels :
+ for t in var.vpn_onprem_configs.dev-primary.tunnels :
"remote-${t.vpn_gateway_interface}-${t.peer_external_gateway_interface}" => {
bgp_peer = {
address = cidrhost(t.session_range, 1)
asn = t.peer_asn
}
- bgp_peer_options = local.bgp_peer_options_onprem.dev-ew1
+ bgp_peer_options = local.bgp_peer_options_onprem.dev-primary
bgp_session_range = "${cidrhost(t.session_range, 2)}/30"
peer_external_gateway_interface = t.peer_external_gateway_interface
shared_secret = t.secret
diff --git a/fast/stages/02-networking-separate-envs/vpn-onprem-prod.tf b/fast/stages/2-networking-d-separate-envs/vpn-onprem-prod.tf
similarity index 73%
rename from fast/stages/02-networking-separate-envs/vpn-onprem-prod.tf
rename to fast/stages/2-networking-d-separate-envs/vpn-onprem-prod.tf
index d4b2af24e..0793e2744 100644
--- a/fast/stages/02-networking-separate-envs/vpn-onprem-prod.tf
+++ b/fast/stages/2-networking-d-separate-envs/vpn-onprem-prod.tf
@@ -16,28 +16,33 @@
# tfdoc:file:description VPN between prod and onprem.
-module "prod-to-onprem-ew1-vpn" {
+moved {
+ from = module.prod-to-onprem-ew1-vpn
+ to = module.prod-to-onprem-primary-vpn
+}
+
+module "prod-to-onprem-primary-vpn" {
count = local.enable_onprem_vpn ? 1 : 0
source = "../../../modules/net-vpn-ha"
project_id = module.prod-spoke-project.project_id
network = module.prod-spoke-vpc.self_link
- region = "europe-west1"
- name = "vpn-to-onprem-ew1"
+ region = var.regions.primary
+ name = "vpn-to-onprem-${local.region_shortnames[var.regions.primary]}"
router_config = {
- name = "prod-onprem-vpn-ew1"
- asn = var.router_onprem_configs.prod-ew1.asn
+ name = "prod-onprem-vpn-${local.region_shortnames[var.regions.primary]}"
+ asn = var.router_onprem_configs.prod-primary.asn
}
peer_gateway = {
- external = var.vpn_onprem_configs.prod-ew1.peer_external_gateway
+ external = var.vpn_onprem_configs.prod-primary.peer_external_gateway
}
tunnels = {
- for t in var.vpn_onprem_configs.prod-ew1.tunnels :
+ for t in var.vpn_onprem_configs.prod-primary.tunnels :
"remote-${t.vpn_gateway_interface}-${t.peer_external_gateway_interface}" => {
bgp_peer = {
address = cidrhost(t.session_range, 1)
asn = t.peer_asn
}
- bgp_peer_options = local.bgp_peer_options_onprem.prod-ew1
+ bgp_peer_options = local.bgp_peer_options_onprem.prod-primary
bgp_session_range = "${cidrhost(t.session_range, 2)}/30"
peer_external_gateway_interface = t.peer_external_gateway_interface
shared_secret = t.secret
diff --git a/fast/stages/02-security/IAM.md b/fast/stages/2-security/IAM.md
similarity index 100%
rename from fast/stages/02-security/IAM.md
rename to fast/stages/2-security/IAM.md
diff --git a/fast/stages/02-security/README.md b/fast/stages/2-security/README.md
similarity index 74%
rename from fast/stages/02-security/README.md
rename to fast/stages/2-security/README.md
index 026ef7717..6486cd741 100644
--- a/fast/stages/02-security/README.md
+++ b/fast/stages/2-security/README.md
@@ -12,6 +12,24 @@ The following diagram illustrates the high-level design of created resources and
object({…}) | ✓ | | 00-bootstrap |
-| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap |
-| [folder_ids](variables.tf#L34) | Folder name => id mappings, the 'security' folder name must exist. | object({…}) | ✓ | | 01-resman |
-| [organization](variables.tf#L80) | Organization details. | object({…}) | ✓ | | 00-bootstrap |
-| [prefix](variables.tf#L96) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap |
-| [service_accounts](variables.tf#L107) | Automation service accounts that can assign the encrypt/decrypt roles on keys. | object({…}) | ✓ | | 01-resman |
-| [groups](variables.tf#L42) | Group names to grant organization-level permissions. | map(string) | | {…} | 00-bootstrap |
-| [kms_defaults](variables.tf#L57) | Defaults used for KMS keys. | object({…}) | | {…} | |
-| [kms_keys](variables.tf#L69) | KMS keys to create, keyed by name. Null attributes will be interpolated with defaults. | map(object({…})) | | {} | |
-| [outputs_location](variables.tf#L90) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | |
-| [vpc_sc_access_levels](variables.tf#L118) | VPC SC access level definitions. | map(object({…})) | | {} | |
-| [vpc_sc_egress_policies](variables.tf#L147) | VPC SC egress policy defnitions. | map(object({…})) | | {} | |
-| [vpc_sc_ingress_policies](variables.tf#L167) | VPC SC ingress policy defnitions. | map(object({…})) | | {} | |
-| [vpc_sc_perimeters](variables.tf#L188) | VPC SC regular perimeter definitions. | object({…}) | | {} | |
+| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap |
+| [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap |
+| [folder_ids](variables.tf#L38) | Folder name => id mappings, the 'security' folder name must exist. | object({…}) | ✓ | | 1-resman |
+| [organization](variables.tf#L84) | Organization details. | object({…}) | ✓ | | 0-bootstrap |
+| [prefix](variables.tf#L100) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap |
+| [service_accounts](variables.tf#L111) | Automation service accounts that can assign the encrypt/decrypt roles on keys. | object({…}) | ✓ | | 1-resman |
+| [groups](variables.tf#L46) | Group names to grant organization-level permissions. | map(string) | | {…} | 0-bootstrap |
+| [kms_defaults](variables.tf#L61) | Defaults used for KMS keys. | object({…}) | | {…} | |
+| [kms_keys](variables.tf#L73) | KMS keys to create, keyed by name. Null attributes will be interpolated with defaults. | map(object({…})) | | {} | |
+| [outputs_location](variables.tf#L94) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | |
+| [vpc_sc_access_levels](variables.tf#L122) | VPC SC access level definitions. | map(object({…})) | | {} | |
+| [vpc_sc_egress_policies](variables.tf#L151) | VPC SC egress policy defnitions. | map(object({…})) | | {} | |
+| [vpc_sc_ingress_policies](variables.tf#L171) | VPC SC ingress policy defnitions. | map(object({…})) | | {} | |
+| [vpc_sc_perimeters](variables.tf#L192) | VPC SC regular perimeter definitions. | object({…}) | | {} | |
## Outputs
diff --git a/fast/stages/02-security/core-dev.tf b/fast/stages/2-security/core-dev.tf
similarity index 100%
rename from fast/stages/02-security/core-dev.tf
rename to fast/stages/2-security/core-dev.tf
diff --git a/fast/stages/02-security/core-prod.tf b/fast/stages/2-security/core-prod.tf
similarity index 100%
rename from fast/stages/02-security/core-prod.tf
rename to fast/stages/2-security/core-prod.tf
diff --git a/fast/stages/02-security/diagram.png b/fast/stages/2-security/diagram.png
similarity index 100%
rename from fast/stages/02-security/diagram.png
rename to fast/stages/2-security/diagram.png
diff --git a/fast/stages/02-security/diagram.svg b/fast/stages/2-security/diagram.svg
similarity index 100%
rename from fast/stages/02-security/diagram.svg
rename to fast/stages/2-security/diagram.svg
diff --git a/fast/stages/02-security/main.tf b/fast/stages/2-security/main.tf
similarity index 100%
rename from fast/stages/02-security/main.tf
rename to fast/stages/2-security/main.tf
diff --git a/fast/stages/02-security/outputs.tf b/fast/stages/2-security/outputs.tf
similarity index 96%
rename from fast/stages/02-security/outputs.tf
rename to fast/stages/2-security/outputs.tf
index b7e42e492..ff0c13eda 100644
--- a/fast/stages/02-security/outputs.tf
+++ b/fast/stages/2-security/outputs.tf
@@ -44,13 +44,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${pathexpand(var.outputs_location)}/tfvars/02-security.auto.tfvars.json"
+ filename = "${pathexpand(var.outputs_location)}/tfvars/2-security.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/02-security.auto.tfvars.json"
+ name = "tfvars/2-security.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/02-security/variables.tf b/fast/stages/2-security/variables.tf
similarity index 91%
rename from fast/stages/02-security/variables.tf
rename to fast/stages/2-security/variables.tf
index 349589c96..e14d63763 100644
--- a/fast/stages/02-security/variables.tf
+++ b/fast/stages/2-security/variables.tf
@@ -15,7 +15,7 @@
*/
variable "automation" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Automation resources created by the bootstrap stage."
type = object({
outputs_bucket = string
@@ -23,16 +23,20 @@ variable "automation" {
}
variable "billing_account" {
- # tfdoc:variable:source 00-bootstrap
- description = "Billing account id and organization id ('nnnnnnnn' or null)."
+ # tfdoc:variable:source 0-bootstrap
+ description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false."
type = object({
- id = string
- organization_id = number
+ id = string
+ is_org_level = optional(bool, true)
})
+ validation {
+ condition = var.billing_account.is_org_level != null
+ error_message = "Invalid `null` value for `billing_account.is_org_level`."
+ }
}
variable "folder_ids" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Folder name => id mappings, the 'security' folder name must exist."
type = object({
security = string
@@ -40,7 +44,7 @@ variable "folder_ids" {
}
variable "groups" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Group names to grant organization-level permissions."
type = map(string)
# https://cloud.google.com/docs/enterprise/setup-checklist
@@ -78,7 +82,7 @@ variable "kms_keys" {
}
variable "organization" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Organization details."
type = object({
domain = string
@@ -94,7 +98,7 @@ variable "outputs_location" {
}
variable "prefix" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Prefix used for resources that need unique names. Use 9 characters or less."
type = string
@@ -105,7 +109,7 @@ variable "prefix" {
}
variable "service_accounts" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Automation service accounts that can assign the encrypt/decrypt roles on keys."
type = object({
data-platform-dev = string
diff --git a/fast/stages/02-security/vpc-sc-restricted-services.yaml b/fast/stages/2-security/vpc-sc-restricted-services.yaml
similarity index 100%
rename from fast/stages/02-security/vpc-sc-restricted-services.yaml
rename to fast/stages/2-security/vpc-sc-restricted-services.yaml
diff --git a/fast/stages/02-security/vpc-sc.tf b/fast/stages/2-security/vpc-sc.tf
similarity index 85%
rename from fast/stages/02-security/vpc-sc.tf
rename to fast/stages/2-security/vpc-sc.tf
index 60767fe5f..953badf15 100644
--- a/fast/stages/02-security/vpc-sc.tf
+++ b/fast/stages/2-security/vpc-sc.tf
@@ -37,11 +37,19 @@ locals {
)
}
# compute spec/status for each perimeter
- vpc_sc_perimeters = {
+ vpc_sc_perimeters_spec_status = {
dev = merge(var.vpc_sc_perimeters.dev, {
restricted_services = local._vpc_sc_restricted_services
vpc_accessible_services = local._vpc_sc_vpc_accessible_services
})
+ landing = merge(var.vpc_sc_perimeters.landing, {
+ restricted_services = local._vpc_sc_restricted_services
+ vpc_accessible_services = local._vpc_sc_vpc_accessible_services
+ })
+ prod = merge(var.vpc_sc_perimeters.prod, {
+ restricted_services = local._vpc_sc_restricted_services
+ vpc_accessible_services = local._vpc_sc_vpc_accessible_services
+ })
}
}
@@ -98,13 +106,13 @@ module "vpc-sc" {
dev = {
spec = (
local.vpc_sc_explicit_dry_run_spec
- ? var.vpc_sc_perimeters.dev
+ ? local.vpc_sc_perimeters_spec_status.dev
: null
)
status = (
local.vpc_sc_explicit_dry_run_spec
? null
- : var.vpc_sc_perimeters.dev
+ : local.vpc_sc_perimeters_spec_status.dev
)
use_explicit_dry_run_spec = local.vpc_sc_explicit_dry_run_spec
}
@@ -114,13 +122,13 @@ module "vpc-sc" {
landing = {
spec = (
local.vpc_sc_explicit_dry_run_spec
- ? var.vpc_sc_perimeters.landing
+ ? local.vpc_sc_perimeters_spec_status.landing
: null
)
status = (
local.vpc_sc_explicit_dry_run_spec
? null
- : var.vpc_sc_perimeters.landing
+ : local.vpc_sc_perimeters_spec_status.landing
)
use_explicit_dry_run_spec = local.vpc_sc_explicit_dry_run_spec
}
@@ -130,13 +138,13 @@ module "vpc-sc" {
prod = {
spec = (
local.vpc_sc_explicit_dry_run_spec
- ? var.vpc_sc_perimeters.prod
+ ? local.vpc_sc_perimeters_spec_status.prod
: null
)
status = (
local.vpc_sc_explicit_dry_run_spec
? null
- : var.vpc_sc_perimeters.prod
+ : local.vpc_sc_perimeters_spec_status.prod
)
use_explicit_dry_run_spec = local.vpc_sc_explicit_dry_run_spec
}
diff --git a/fast/stages/03-data-platform/README.md b/fast/stages/3-data-platform/README.md
similarity index 100%
rename from fast/stages/03-data-platform/README.md
rename to fast/stages/3-data-platform/README.md
diff --git a/fast/stages/03-data-platform/dev/IAM.md b/fast/stages/3-data-platform/dev/IAM.md
similarity index 100%
rename from fast/stages/03-data-platform/dev/IAM.md
rename to fast/stages/3-data-platform/dev/IAM.md
diff --git a/fast/stages/3-data-platform/dev/README.md b/fast/stages/3-data-platform/dev/README.md
new file mode 100644
index 000000000..48d09eafc
--- /dev/null
+++ b/fast/stages/3-data-platform/dev/README.md
@@ -0,0 +1,218 @@
+# Data Platform
+
+The Data Platform builds on top of your foundations to create and set up projects (and related resources) to be used for your data platform.
+
+
+
+
+
+
data-platform-foundations | |
+| [outputs.tf](./outputs.tf) | Output variables. | | google_storage_bucket_object · local_file |
+| [variables.tf](./variables.tf) | Terraform Variables. | | |
+
+## Variables
+
+| name | description | type | required | default | producer |
+|---|---|:---:|:---:|:---:|:---:|
+| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap |
+| [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap |
+| [folder_ids](variables.tf#L102) | Folder to be used for the networking resources in folders/nnnn format. | object({…}) | ✓ | | 1-resman |
+| [host_project_ids](variables.tf#L120) | Shared VPC project ids. | object({…}) | ✓ | | 2-networking |
+| [organization](variables.tf#L150) | Organization details. | object({…}) | ✓ | | 00-globals |
+| [prefix](variables.tf#L166) | Unique prefix used for resource names. Not used for projects if 'project_create' is null. | string | ✓ | | 00-globals |
+| [composer_config](variables.tf#L38) | Cloud Composer configuration options. | object({…}) | | {…} | |
+| [data_catalog_tags](variables.tf#L85) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {…} | |
+| [data_force_destroy](variables.tf#L96) | Flag to set 'force_destroy' on data services like BigQery or Cloud Storage. | bool | | false | |
+| [groups](variables.tf#L110) | Groups. | map(string) | | {…} | |
+| [location](variables.tf#L128) | Location used for multi-regional resources. | string | | "eu" | |
+| [network_config_composer](variables.tf#L134) | Network configurations to use for Composer. | object({…}) | | {…} | |
+| [outputs_location](variables.tf#L160) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | |
+| [project_services](variables.tf#L172) | List of core services enabled on all projects. | list(string) | | […] | |
+| [region](variables.tf#L183) | Region used for regional resources. | string | | "europe-west1" | |
+| [service_encryption_keys](variables.tf#L189) | Cloud KMS to use to encrypt different services. Key location should match service region. | object({…}) | | null | |
+| [subnet_self_links](variables.tf#L201) | Shared VPC subnet self links. | object({…}) | | null | 2-networking |
+| [vpc_self_links](variables.tf#L210) | Shared VPC self links. | object({…}) | | null | 2-networking |
+
+## Outputs
+
+| name | description | sensitive | consumers |
+|---|---|:---:|---|
+| [bigquery_datasets](outputs.tf#L42) | BigQuery datasets. | | |
+| [demo_commands](outputs.tf#L47) | Demo commands. | | |
+| [gcs_buckets](outputs.tf#L52) | GCS buckets. | | |
+| [kms_keys](outputs.tf#L57) | Cloud MKS keys. | | |
+| [projects](outputs.tf#L62) | GCP Projects informations. | | |
+| [vpc_network](outputs.tf#L67) | VPC network. | | |
+| [vpc_subnet](outputs.tf#L72) | VPC subnetworks. | | |
+
+
diff --git a/fast/stages/03-data-platform/dev/demo b/fast/stages/3-data-platform/dev/demo
similarity index 100%
rename from fast/stages/03-data-platform/dev/demo
rename to fast/stages/3-data-platform/dev/demo
diff --git a/fast/stages/03-data-platform/dev/diagram.png b/fast/stages/3-data-platform/dev/diagram.png
similarity index 100%
rename from fast/stages/03-data-platform/dev/diagram.png
rename to fast/stages/3-data-platform/dev/diagram.png
diff --git a/fast/stages/03-data-platform/dev/diagram_vpcsc.png b/fast/stages/3-data-platform/dev/diagram_vpcsc.png
similarity index 100%
rename from fast/stages/03-data-platform/dev/diagram_vpcsc.png
rename to fast/stages/3-data-platform/dev/diagram_vpcsc.png
diff --git a/fast/stages/03-data-platform/dev/main.tf b/fast/stages/3-data-platform/dev/main.tf
similarity index 97%
rename from fast/stages/03-data-platform/dev/main.tf
rename to fast/stages/3-data-platform/dev/main.tf
index 24abb58d9..53d901d1b 100644
--- a/fast/stages/03-data-platform/dev/main.tf
+++ b/fast/stages/3-data-platform/dev/main.tf
@@ -37,7 +37,6 @@ module "data-platform" {
composer_ip_ranges = {
cloudsql = var.network_config_composer.cloudsql_range
gke_master = var.network_config_composer.gke_master_range
- web_server = var.network_config_composer.web_server_range
}
composer_secondary_ranges = {
pods = var.network_config_composer.gke_pods_name
diff --git a/fast/stages/03-data-platform/dev/outputs.tf b/fast/stages/3-data-platform/dev/outputs.tf
similarity index 95%
rename from fast/stages/03-data-platform/dev/outputs.tf
rename to fast/stages/3-data-platform/dev/outputs.tf
index d0f79358c..2eb813b4d 100644
--- a/fast/stages/03-data-platform/dev/outputs.tf
+++ b/fast/stages/3-data-platform/dev/outputs.tf
@@ -27,13 +27,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${pathexpand(var.outputs_location)}/tfvars/03-data-platform-dev.auto.tfvars.json"
+ filename = "${pathexpand(var.outputs_location)}/tfvars/3-data-platform-dev.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/03-data-platform-dev.auto.tfvars.json"
+ name = "tfvars/3-data-platform-dev.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/03-data-platform/dev/variables.tf b/fast/stages/3-data-platform/dev/variables.tf
similarity index 72%
rename from fast/stages/03-data-platform/dev/variables.tf
rename to fast/stages/3-data-platform/dev/variables.tf
index 9495316a9..74a5dbe11 100644
--- a/fast/stages/03-data-platform/dev/variables.tf
+++ b/fast/stages/3-data-platform/dev/variables.tf
@@ -15,7 +15,7 @@
# tfdoc:file:description Terraform Variables.
variable "automation" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Automation resources created by the bootstrap stage."
type = object({
outputs_bucket = string
@@ -23,25 +23,62 @@ variable "automation" {
}
variable "billing_account" {
- # tfdoc:variable:source 00-globals
- description = "Billing account id and organization id ('nnnnnnnn' or null)."
+ # tfdoc:variable:source 0-bootstrap
+ description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false."
type = object({
- id = string
- organization_id = number
+ id = string
+ is_org_level = optional(bool, true)
})
+ validation {
+ condition = var.billing_account.is_org_level != null
+ error_message = "Invalid `null` value for `billing_account.is_org_level`."
+ }
}
variable "composer_config" {
description = "Cloud Composer configuration options."
type = object({
- node_count = number
- airflow_version = string
- env_variables = map(string)
+ disable_deployment = optional(bool)
+ environment_size = string
+ software_config = object({
+ airflow_config_overrides = optional(any)
+ pypi_packages = optional(any)
+ env_variables = optional(map(string))
+ image_version = string
+ })
+ workloads_config = object({
+ scheduler = object(
+ {
+ cpu = number
+ memory_gb = number
+ storage_gb = number
+ count = number
+ }
+ )
+ web_server = object(
+ {
+ cpu = number
+ memory_gb = number
+ storage_gb = number
+ }
+ )
+ worker = object(
+ {
+ cpu = number
+ memory_gb = number
+ storage_gb = number
+ min_count = number
+ max_count = number
+ }
+ )
+ })
})
default = {
- node_count = 3
- airflow_version = "composer-1.17.5-airflow-2.1.4"
- env_variables = {}
+ environment_size = "ENVIRONMENT_SIZE_SMALL"
+ software_config = {
+ image_version = "composer-2-airflow-2"
+ }
+ workloads_config = null
}
}
@@ -63,7 +100,7 @@ variable "data_force_destroy" {
}
variable "folder_ids" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Folder to be used for the networking resources in folders/nnnn format."
type = object({
data-platform-dev = string
@@ -81,7 +118,7 @@ variable "groups" {
}
variable "host_project_ids" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Shared VPC project ids."
type = object({
dev-spoke-0 = string
@@ -101,14 +138,12 @@ variable "network_config_composer" {
gke_master_range = string
gke_pods_name = string
gke_services_name = string
- web_server_range = string
})
default = {
cloudsql_range = "192.168.254.0/24"
gke_master_range = "192.168.255.0/28"
gke_pods_name = "pods"
gke_services_name = "services"
- web_server_range = "192.168.255.16/28"
}
}
@@ -164,7 +199,7 @@ variable "service_encryption_keys" {
}
variable "subnet_self_links" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Shared VPC subnet self links."
type = object({
dev-spoke-0 = map(string)
@@ -173,7 +208,7 @@ variable "subnet_self_links" {
}
variable "vpc_self_links" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Shared VPC self links."
type = object({
dev-spoke-0 = string
diff --git a/fast/stages/03-gke-multitenant/README.md b/fast/stages/3-gke-multitenant/README.md
similarity index 71%
rename from fast/stages/03-gke-multitenant/README.md
rename to fast/stages/3-gke-multitenant/README.md
index f08910c83..9f9d9498e 100644
--- a/fast/stages/03-gke-multitenant/README.md
+++ b/fast/stages/3-gke-multitenant/README.md
@@ -2,7 +2,7 @@
This directory contains a stage that can be used to centralize management of GKE multinenant clusters.
-The Terraform code follows the same general approach used for the [project factory](../03-project-factory/) and [data platform](../03-data-platform/) stages, where a "fat module" contains the stage code and is used by thin code wrappers that localize it for each environment or specialized configuration:
+The Terraform code follows the same general approach used for the [project factory](../3-project-factory/) and [data platform](../3-data-platform/) stages, where a "fat module" contains the stage code and is used by thin code wrappers that localize it for each environment or specialized configuration:
The [`dev` folder](./dev/) contains an example setup for a generic development environment, and can be used as-is or cloned to implement other environments, or more specialized setups
diff --git a/fast/stages/03-gke-multitenant/dev/README.md b/fast/stages/3-gke-multitenant/dev/README.md
similarity index 73%
rename from fast/stages/03-gke-multitenant/dev/README.md
rename to fast/stages/3-gke-multitenant/dev/README.md
index c446fbcb4..f0460c06c 100644
--- a/fast/stages/03-gke-multitenant/dev/README.md
+++ b/fast/stages/3-gke-multitenant/dev/README.md
@@ -39,7 +39,68 @@ This stage creates a project containing and as many clusters and node pools as r
## How to run this stage
-This stage is meant to be executed after "foundational stages" (i.e., stages [`00-bootstrap`](../../00-bootstrap), [`01-resman`](../../01-resman), 02-networking (either [VPN](../../02-networking-vpn) or [NVA](../../02-networking-nva)) and [`02-security`](../../02-security)) have been run.
+This stage is meant to be executed after the FAST "foundational" stages: bootstrap, resource management, security and networking stages.
+
+It's of course possible to run this stage in isolation, refer to the *[Running in isolation](#running-in-isolation)* section below for details.
+
+Before running this stage, you need to make sure you have the correct credentials and permissions, and localize variables by assigning values that match your configuration.
+
+### Provider and Terraform variables
+
+As all other FAST stages, the [mechanism used to pass variable values and pre-built provider files from one stage to the next](../../0-bootstrap/README.md#output-files-and-cross-stage-variables) is also leveraged here.
+
+The commands to link or copy the provider and terraform variable files can be easily derived from the `stage-links.sh` script in the FAST root folder, passing it a single argument with the local output files folder (if configured) or the GCS output bucket in the automation project (derived from stage 0 outputs). The following examples demonstrate both cases, and the resulting commands that then need to be copy/pasted and run.
+
+```bash
+../../../stage-links.sh ~/fast-config
+
+# copy and paste the following commands for '3-gke-multitenant'
+
+ln -s /home/ludomagno/fast-config/providers/3-gke-multitenant-providers.tf ./
+ln -s /home/ludomagno/fast-config/tfvars/globals.auto.tfvars.json ./
+ln -s /home/ludomagno/fast-config/tfvars/0-bootstrap.auto.tfvars.json ./
+ln -s /home/ludomagno/fast-config/tfvars/1-resman.auto.tfvars.json ./
+ln -s /home/ludomagno/fast-config/tfvars/2-networking.auto.tfvars.json ./
+ln -s /home/ludomagno/fast-config/tfvars/2-security.auto.tfvars.json ./
+```
+
+```bash
+../../../stage-links.sh gs://xxx-prod-iac-core-outputs-0
+
+# copy and paste the following commands for '3-gke-multitenant'
+
+gcloud alpha storage cp gs://xxx-prod-iac-core-outputs-0/providers/3-gke-multitenant-providers.tf ./
+gcloud alpha storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/globals.auto.tfvars.json ./
+gcloud alpha storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/0-bootstrap.auto.tfvars.json ./
+gcloud alpha storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/1-resman.auto.tfvars.json ./
+gcloud alpha storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/2-networking.auto.tfvars.json ./
+gcloud alpha storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/2-security.auto.tfvars.json ./
+```
+
+### Impersonating the automation service account
+
+The preconfigured provider file uses impersonation to run with this stage's automation service account's credentials. The `gcp-devops` and `organization-admins` groups have the necessary IAM bindings in place to do that, so make sure the current user is a member of one of those groups.
+
+### Variable configuration
+
+Variables in this stage -- like most other FAST stages -- are broadly divided into three separate sets:
+
+- variables which refer to global values for the whole organization (org id, billing account id, prefix, etc.), which are pre-populated via the `globals.auto.tfvars.json` file linked or copied above
+- variables which refer to resources managed by previous stage, which are prepopulated here via the `*.auto.tfvars.json` files linked or copied above
+- and finally variables that optionally control this stage's behaviour and customizations, and can to be set in a custom `terraform.tfvars` file
+
+The latter set is explained in the [Customization](#customizations) sections below, and the full list can be found in the [Variables](#variables) table at the bottom of this document.
+
+### Running the stage
+
+Once provider and variable values are in place and the correct user is configured, the stage can be run:
+
+```bash
+terraform init
+terraform apply
+```
+
+### Running in isolation
It's of course possible to run this stage in isolation, by making sure the architectural prerequisites are satisfied (e.g., networking), and that the Service Account running the stage is granted the roles/permissions below:
@@ -62,39 +123,9 @@ It's of course possible to run this stage in isolation, by making sure the archi
The VPC host project, VPC and subnets should already exist.
-### Providers configuration
+## Customizations
-If you're running this on top of FAST, you should run the following commands to create the providers file, and populate the required variables from the previous stage.
-
-```bash
-# Variable `outputs_location` is set to `~/fast-config` in stage 01-resman
-$ cd fabric-fast/stages/03-gke-multitenant/dev
-ln -s ~/fast-config/providers/03-gke-dev-providers.tf .
-```
-
-### Variable configuration
-
-There are two broad sets of variables you will need to fill in:
-
-- variables shared by other stages (organization id, billing account id, etc.), or derived from a resource managed by a different stage (folder id, automation project id, etc.)
-- variables specific to resources managed by this stage
-
-#### Variables passed in from other stages
-
-To avoid the tedious job of filling in the first group of variables with values derived from other stages' outputs, the same mechanism used above for the provider configuration can be used to leverage pre-configured `.tfvars` files.
-
-If you configured a valid path for `outputs_location` in the bootstrap and networking stage, simply link the relevant `terraform-*.auto.tfvars.json` files from this stage's outputs folder (under the path you specified), where the `*` above is set to the name of the stage that produced it. For this stage, a single `.tfvars` file is available:
-
-```bash
-# Variable `outputs_location` is set to `~/fast-config`
-ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json .
-ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json .
-ln -s ~/fast-config/tfvars/02-networking.auto.tfvars.json .
-```
-
-If you're not using FAST, refer to the [Variables](#variables) table at the bottom of this document for a full list of variables, their origin (e.g., a stage or specific to this one), and descriptions explaining their meaning.
-
-#### Cluster and node pools
+### Cluster and node pools
This stage is designed with multi-tenancy in mind, and the expectation is that GKE clusters will mostly share a common set of defaults. Variables are designed to support this approach for both clusters and node pools:
@@ -105,7 +136,7 @@ This stage is designed with multi-tenancy in mind, and the expectation is that
There are two additional variables that influence cluster configuration: `authenticator_security_group` to configure [Google Groups for RBAC](https://cloud.google.com/kubernetes-engine/docs/how-to/google-groups-rbac), `dns_domain` to configure [Cloud DNS for GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-dns).
-#### Fleet management
+### Fleet management
Fleet management is entirely optional, and uses three separate variables:
@@ -116,15 +147,6 @@ Fleet management is entirely optional, and uses three separate variables:
Leave all these variables unset (or set to `null`) to disable fleet management.
-## Running Terraform
-
-Once the [provider](#providers-configuration) and [variable](#variable-configuration) configuration is complete, you can apply this stage:
-
-```bash
-terraform init
-terraform apply
-```
-
@@ -140,23 +162,23 @@ terraform apply
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L21) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 00-bootstrap |
-| [billing_account](variables.tf#L29) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap |
-| [folder_ids](variables.tf#L149) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 01-resman |
-| [host_project_ids](variables.tf#L164) | Host project for the shared VPC. | object({…}) | ✓ | | 02-networking |
-| [prefix](variables.tf#L213) | Prefix used for resources that need unique names. | string | ✓ | | |
-| [vpc_self_links](variables.tf#L225) | Self link for the shared VPC. | object({…}) | ✓ | | 02-networking |
-| [clusters](variables.tf#L38) | Clusters configuration. Refer to the gke-cluster module for type details. | map(object({…})) | | {} | |
-| [fleet_configmanagement_clusters](variables.tf#L86) | Config management features enabled on specific sets of member clusters, in config name => [cluster name] format. | map(list(string)) | | {} | |
-| [fleet_configmanagement_templates](variables.tf#L94) | Sets of config management configurations that can be applied to member clusters, in config name => {options} format. | map(object({…})) | | {} | |
-| [fleet_features](variables.tf#L129) | Enable and configue fleet features. Set to null to disable GKE Hub if fleet workload identity is not used. | object({…}) | | null | |
-| [fleet_workload_identity](variables.tf#L142) | Use Fleet Workload Identity for clusters. Enables GKE Hub if set to true. | bool | | false | |
-| [group_iam](variables.tf#L157) | Project-level authoritative IAM bindings for groups in {GROUP_EMAIL => [ROLES]} format. Use group emails as keys, list of roles as values. | map(list(string)) | | {} | |
-| [iam](variables.tf#L172) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | |
-| [labels](variables.tf#L179) | Project-level labels. | map(string) | | {} | |
-| [nodepools](variables.tf#L185) | Nodepools configuration. Refer to the gke-nodepool module for type details. | map(map(object({…}))) | | {} | |
-| [outputs_location](variables.tf#L207) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | |
-| [project_services](variables.tf#L218) | Additional project services to enable. | list(string) | | [] | |
+| [automation](variables.tf#L21) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap |
+| [billing_account](variables.tf#L29) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap |
+| [folder_ids](variables.tf#L153) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 1-resman |
+| [host_project_ids](variables.tf#L168) | Host project for the shared VPC. | object({…}) | ✓ | | 2-networking |
+| [prefix](variables.tf#L217) | Prefix used for resources that need unique names. | string | ✓ | | |
+| [vpc_self_links](variables.tf#L229) | Self link for the shared VPC. | object({…}) | ✓ | | 2-networking |
+| [clusters](variables.tf#L42) | Clusters configuration. Refer to the gke-cluster module for type details. | map(object({…})) | | {} | |
+| [fleet_configmanagement_clusters](variables.tf#L90) | Config management features enabled on specific sets of member clusters, in config name => [cluster name] format. | map(list(string)) | | {} | |
+| [fleet_configmanagement_templates](variables.tf#L98) | Sets of config management configurations that can be applied to member clusters, in config name => {options} format. | map(object({…})) | | {} | |
+| [fleet_features](variables.tf#L133) | Enable and configue fleet features. Set to null to disable GKE Hub if fleet workload identity is not used. | object({…}) | | null | |
+| [fleet_workload_identity](variables.tf#L146) | Use Fleet Workload Identity for clusters. Enables GKE Hub if set to true. | bool | | false | |
+| [group_iam](variables.tf#L161) | Project-level authoritative IAM bindings for groups in {GROUP_EMAIL => [ROLES]} format. Use group emails as keys, list of roles as values. | map(list(string)) | | {} | |
+| [iam](variables.tf#L176) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | |
+| [labels](variables.tf#L183) | Project-level labels. | map(string) | | {} | |
+| [nodepools](variables.tf#L189) | Nodepools configuration. Refer to the gke-nodepool module for type details. | map(map(object({…}))) | | {} | |
+| [outputs_location](variables.tf#L211) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string | | null | |
+| [project_services](variables.tf#L222) | Additional project services to enable. | list(string) | | [] | |
## Outputs
diff --git a/fast/stages/03-gke-multitenant/dev/diagram.png b/fast/stages/3-gke-multitenant/dev/diagram.png
similarity index 100%
rename from fast/stages/03-gke-multitenant/dev/diagram.png
rename to fast/stages/3-gke-multitenant/dev/diagram.png
diff --git a/fast/stages/03-gke-multitenant/dev/main.tf b/fast/stages/3-gke-multitenant/dev/main.tf
similarity index 100%
rename from fast/stages/03-gke-multitenant/dev/main.tf
rename to fast/stages/3-gke-multitenant/dev/main.tf
diff --git a/fast/stages/03-gke-multitenant/dev/outputs.tf b/fast/stages/3-gke-multitenant/dev/outputs.tf
similarity index 96%
rename from fast/stages/03-gke-multitenant/dev/outputs.tf
rename to fast/stages/3-gke-multitenant/dev/outputs.tf
index 87b0ca737..3f231c682 100644
--- a/fast/stages/03-gke-multitenant/dev/outputs.tf
+++ b/fast/stages/3-gke-multitenant/dev/outputs.tf
@@ -42,13 +42,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${pathexpand(var.outputs_location)}/tfvars/03-gke-dev.auto.tfvars.json"
+ filename = "${pathexpand(var.outputs_location)}/tfvars/3-gke-dev.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/03-gke-dev.auto.tfvars.json"
+ name = "tfvars/3-gke-dev.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/03-gke-multitenant/dev/variables.tf b/fast/stages/3-gke-multitenant/dev/variables.tf
similarity index 92%
rename from fast/stages/03-gke-multitenant/dev/variables.tf
rename to fast/stages/3-gke-multitenant/dev/variables.tf
index 6be89126a..2dbf5a6ea 100644
--- a/fast/stages/03-gke-multitenant/dev/variables.tf
+++ b/fast/stages/3-gke-multitenant/dev/variables.tf
@@ -19,7 +19,7 @@
# cloud dns for gke?
variable "automation" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Automation resources created by the bootstrap stage."
type = object({
outputs_bucket = string
@@ -27,12 +27,16 @@ variable "automation" {
}
variable "billing_account" {
- # tfdoc:variable:source 00-bootstrap
- description = "Billing account id and organization id ('nnnnnnnn' or null)."
+ # tfdoc:variable:source 0-bootstrap
+ description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false."
type = object({
- id = string
- organization_id = number
+ id = string
+ is_org_level = optional(bool, true)
})
+ validation {
+ condition = var.billing_account.is_org_level != null
+ error_message = "Invalid `null` value for `billing_account.is_org_level`."
+ }
}
variable "clusters" {
@@ -147,7 +151,7 @@ variable "fleet_workload_identity" {
}
variable "folder_ids" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created."
type = object({
gke-dev = string
@@ -162,7 +166,7 @@ variable "group_iam" {
}
variable "host_project_ids" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Host project for the shared VPC."
type = object({
dev-spoke-0 = string
@@ -223,7 +227,7 @@ variable "project_services" {
}
variable "vpc_self_links" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Self link for the shared VPC."
type = object({
dev-spoke-0 = string
diff --git a/fast/stages/03-project-factory/README.md b/fast/stages/3-project-factory/README.md
similarity index 100%
rename from fast/stages/03-project-factory/README.md
rename to fast/stages/3-project-factory/README.md
diff --git a/fast/stages/03-project-factory/dev/README.md b/fast/stages/3-project-factory/dev/README.md
similarity index 85%
rename from fast/stages/03-project-factory/dev/README.md
rename to fast/stages/3-project-factory/dev/README.md
index 8fe213cee..2d95f918d 100644
--- a/fast/stages/03-project-factory/dev/README.md
+++ b/fast/stages/3-project-factory/dev/README.md
@@ -28,7 +28,7 @@ The project factory takes care of the following activities:
## How to run this stage
-This stage is meant to be executed after "foundational stages" (i.e., stages [`00-bootstrap`](../../00-bootstrap), [`01-resman`](../../01-resman), 02-networking (either [VPN](../../02-networking-vpn) or [NVA](../../02-networking-nva)) and [`02-security`](../../02-security)) have been run.
+This stage is meant to be executed after "foundational stages" (i.e., stages [`00-bootstrap`](../../0-bootstrap), [`01-resman`](../../1-resman), 02-networking (either [VPN](../../2-networking-b-vpn) or [NVA](../../2-networking-c-nva)) and [`02-security`](../../2-security)) have been run.
It's of course possible to run this stage in isolation, by making sure the architectural prerequisites are satisfied (e.g., networking), and that the Service Account running the stage is granted the roles/permissions below:
@@ -108,13 +108,13 @@ terraform apply
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [billing_account](variables.tf#L19) | Billing account id and organization id ('nnnnnnnn' or null). | object({…}) | ✓ | | 00-bootstrap |
-| [prefix](variables.tf#L56) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 00-bootstrap |
-| [data_dir](variables.tf#L28) | Relative path for the folder storing configuration data. | string | | "data/projects" | |
-| [defaults_file](variables.tf#L34) | Relative path for the file storing the project factory configuration. | string | | "data/defaults.yaml" | |
-| [environment_dns_zone](variables.tf#L40) | DNS zone suffix for environment. | string | | null | 02-networking |
-| [host_project_ids](variables.tf#L47) | Host project for the shared VPC. | object({…}) | | null | 02-networking |
-| [vpc_self_links](variables.tf#L67) | Self link for the shared VPC. | object({…}) | | null | 02-networking |
+| [billing_account](variables.tf#L19) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap |
+| [prefix](variables.tf#L60) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap |
+| [data_dir](variables.tf#L32) | Relative path for the folder storing configuration data. | string | | "data/projects" | |
+| [defaults_file](variables.tf#L38) | Relative path for the file storing the project factory configuration. | string | | "data/defaults.yaml" | |
+| [environment_dns_zone](variables.tf#L44) | DNS zone suffix for environment. | string | | null | 2-networking |
+| [host_project_ids](variables.tf#L51) | Host project for the shared VPC. | object({…}) | | null | 2-networking |
+| [vpc_self_links](variables.tf#L71) | Self link for the shared VPC. | object({…}) | | null | 2-networking |
## Outputs
diff --git a/fast/stages/03-project-factory/dev/data/defaults.yaml b/fast/stages/3-project-factory/dev/data/defaults.yaml
similarity index 100%
rename from fast/stages/03-project-factory/dev/data/defaults.yaml
rename to fast/stages/3-project-factory/dev/data/defaults.yaml
diff --git a/fast/stages/03-project-factory/dev/data/projects/project.yaml.sample b/fast/stages/3-project-factory/dev/data/projects/project.yaml.sample
similarity index 100%
rename from fast/stages/03-project-factory/dev/data/projects/project.yaml.sample
rename to fast/stages/3-project-factory/dev/data/projects/project.yaml.sample
diff --git a/fast/stages/03-project-factory/dev/diagram.png b/fast/stages/3-project-factory/dev/diagram.png
similarity index 100%
rename from fast/stages/03-project-factory/dev/diagram.png
rename to fast/stages/3-project-factory/dev/diagram.png
diff --git a/fast/stages/03-project-factory/dev/diagram.svg b/fast/stages/3-project-factory/dev/diagram.svg
similarity index 100%
rename from fast/stages/03-project-factory/dev/diagram.svg
rename to fast/stages/3-project-factory/dev/diagram.svg
diff --git a/fast/stages/03-project-factory/dev/main.tf b/fast/stages/3-project-factory/dev/main.tf
similarity index 100%
rename from fast/stages/03-project-factory/dev/main.tf
rename to fast/stages/3-project-factory/dev/main.tf
diff --git a/fast/stages/03-project-factory/dev/outputs.tf b/fast/stages/3-project-factory/dev/outputs.tf
similarity index 100%
rename from fast/stages/03-project-factory/dev/outputs.tf
rename to fast/stages/3-project-factory/dev/outputs.tf
diff --git a/fast/stages/03-project-factory/dev/variables.tf b/fast/stages/3-project-factory/dev/variables.tf
similarity index 76%
rename from fast/stages/03-project-factory/dev/variables.tf
rename to fast/stages/3-project-factory/dev/variables.tf
index 2993bfba7..5ad49f772 100644
--- a/fast/stages/03-project-factory/dev/variables.tf
+++ b/fast/stages/3-project-factory/dev/variables.tf
@@ -17,12 +17,16 @@
#TODO: tfdoc annotations
variable "billing_account" {
- # tfdoc:variable:source 00-bootstrap
- description = "Billing account id and organization id ('nnnnnnnn' or null)."
+ # tfdoc:variable:source 0-bootstrap
+ description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false."
type = object({
- id = string
- organization_id = number
+ id = string
+ is_org_level = optional(bool, true)
})
+ validation {
+ condition = var.billing_account.is_org_level != null
+ error_message = "Invalid `null` value for `billing_account.is_org_level`."
+ }
}
variable "data_dir" {
@@ -38,14 +42,14 @@ variable "defaults_file" {
}
variable "environment_dns_zone" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "DNS zone suffix for environment."
type = string
default = null
}
variable "host_project_ids" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Host project for the shared VPC."
type = object({
dev-spoke-0 = string
@@ -54,7 +58,7 @@ variable "host_project_ids" {
}
variable "prefix" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Prefix used for resources that need unique names. Use 9 characters or less."
type = string
@@ -65,7 +69,7 @@ variable "prefix" {
}
variable "vpc_self_links" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Self link for the shared VPC."
type = object({
dev-spoke-0 = string
diff --git a/fast/stages/CLEANUP.md b/fast/stages/CLEANUP.md
index e5f1418f7..4b2667c83 100644
--- a/fast/stages/CLEANUP.md
+++ b/fast/stages/CLEANUP.md
@@ -1,4 +1,5 @@
# FAST deployment clean up
+
If you want to destroy a previous FAST deployment in your organization, follow these steps.
Destruction must be done in reverse order, from stage 3 to stage 0
@@ -6,15 +7,16 @@ Destruction must be done in reverse order, from stage 3 to stage 0
## Stage 3 (Project Factory)
```bash
-cd $FAST_PWD/03-project-factory/prod/
+cd $FAST_PWD/3-project-factory/dev/
terraform destroy
```
## Stage 3 (GKE)
+
Terraform refuses to delete non-empty GCS buckets and BigQuery datasets, so they need to be removed manually from the state.
```bash
-cd $FAST_PWD/03-project-factory/prod/
+cd $FAST_PWD/3-gke-multitenant/dev/
# remove BQ dataset manually
for x in $(terraform state list | grep google_bigquery_dataset); do
@@ -24,16 +26,17 @@ done
terraform destroy
```
-
## Stage 2 (Security)
+
```bash
-cd $FAST_PWD/02-security/
+cd $FAST_PWD/2-security/
terraform destroy
```
## Stage 2 (Networking)
+
```bash
-cd $FAST_PWD/02-networking-XXX/
+cd $FAST_PWD/2-networking-XXX/
terraform destroy
```
@@ -43,9 +46,8 @@ A minor glitch can surface running `terraform destroy`, where the service projec
Stage 1 is a little more complicated because of the GCS buckets containing your terraform statefiles. By default, Terraform refuses to delete non-empty buckets, which is good to protect your terraform state, but it makes destruction a bit harder. Use the commands below to remove the GCS buckets from the state and then execute `terraform destroy`
-
```bash
-cd $FAST_PWD/01-resman/
+cd $FAST_PWD/1-resman/
# remove buckets from state since terraform refuses to delete them
for x in $(terraform state list | grep google_storage_bucket.bucket); do
@@ -62,10 +64,10 @@ terraform destroy
Just like before, we manually remove several resources (GCS buckets and BQ datasets). Note that `terrafom destroy` will fail. This is expected; just continue with the rest of the steps.
```bash
-cd $FAST_PWD/00-bootstrap/
+cd $FAST_PWD/0-bootstrap/
# remove provider config to execute without SA impersonation
-rm 00-bootstrap-providers.tf
+rm 0-bootstrap-providers.tf
# migrate to local state
terraform init -migrate-state
@@ -110,5 +112,6 @@ rm -i terraform.tfstate*
```
In case you want to deploy FAST stages again, the make sure to:
-* Modify the [prefix](00-bootstrap/variables.tf) variable to allow the deployment of resources that need unique names (eg, projects).
-* Modify the [custom_roles](00-bootstrap/variables.tf) variable to allow recently deleted custom roles to be created again.
+
+* Modify the [prefix](0-bootstrap/variables.tf) variable to allow the deployment of resources that need unique names (eg, projects).
+* Modify the [custom_roles](0-bootstrap/variables.tf) variable to allow recently deleted custom roles to be created again.
diff --git a/fast/stages/COMPANION.md b/fast/stages/COMPANION.md
index e0f6ec621..96506d008 100644
--- a/fast/stages/COMPANION.md
+++ b/fast/stages/COMPANION.md
@@ -1,32 +1,42 @@
# FAST deployment companion guide
-To deploy a GCP Landing Zone using FAST, your organization needs to meet a few prerequisites before starting. This guide serves as quick guide to prepare your GCP organization and also as cheat sheet with the commands and minimal configuration required to deploy FAST.
+To deploy a GCP Landing Zone using FAST, your organization needs to meet a few prerequisites before starting. This guide serves as quick guide to prepare your GCP organization and also as cheat sheet with the commands and minimal configuration required to deploy FAST.
The detailed explanation of each stage, their configuration, possible modifications and adaptations are included in the README of stage. This document only outlines the minimal configuration to get from an empty organization to a working FAST deployment.
**Warning! Executing FAST sets organization policies and authoritative role bindings in your GCP Organization. We recommend using FAST on a clean organization, or to fork and adapt FAST to support your existing Organization needs.**
## Prerequisites
-1. FAST uses the recommended groups from the [GCP Enterprise Setup checklist](). Go to [Workspace / Cloud Identity](https://admin.google.com) and ensure all the following groups exist:
- - `gcp-billing-admins@`
- - `gcp-devops@`
- - `gcp-network-admins@`
- - `gcp-organization-admins@`
- - `gcp-security-admins@`
- - `gcp-support@`
+
+1. FAST uses the recommended groups from the [GCP Enterprise Setup checklist](https://cloud.google.com/docs/enterprise/setup-checklist). Go to [Workspace / Cloud Identity](https://admin.google.com) and ensure all the following groups exist:
+
+- `gcp-billing-admins@`
+- `gcp-devops@`
+- `gcp-network-admins@`
+- `gcp-organization-admins@`
+- `gcp-security-admins@`
+- `gcp-support@`
+
2. If you already executed FAST in your organization, make you [clean it up](CLEANUP.md) before continuing with the rest of this guide.
+
3. Grant your user “Organization Administrator” role in your organization and add it to the `gcp-organization-admins@` group.
+
4. Login with your user using gcloud.
+
```bash
gcloud auth login
gcloud auth application-default login
```
+
5. Clone the Fabric repository.
+
```bash
git clone https://github.com/GoogleCloudPlatform/cloud-foundation-fabric.git
cd cloud-foundation-fabric
```
+
6. Grant required roles to your user.
+
```bash
# set a variable to the fast folder
export FAST_PWD="$(pwd)/fast/stages"
@@ -49,9 +59,11 @@ gcloud organizations add-iam-policy-binding $FAST_ORG_ID \
--member user:$FAST_BU --role $role
done
```
-7. Configure Billing Account permissions.
+
+7. Configure Billing Account permissions.
If you are using a standalone billing account, the user applying this stage for the first time needs to be a Billing Administrator.
+
```bash
# find your billing account id with gcloud beta billing accounts list
# replace with your billing id!
@@ -60,20 +72,24 @@ export FAST_BA_ID=XXXXXX-YYYYYY-ZZZZZZ
gcloud beta billing accounts add-iam-policy-binding $FAST_BA_ID \
--member user:$FAST_BU --role roles/billing.admin
```
-If you are using a billing account in a different organization, please follow [these steps](00-bootstrap#billing-account-in-a-different-organization) instead.
+
+If you are using a billing account in a different organization, please follow [these steps](0-bootstrap#billing-account-in-a-different-organization) instead.
## Stage 0 (Bootstrap)
+
This initial stage will create common projects for IaC, Logging & Billing, and bootstrap IAM policies.
```bash
-# move to the 00-bootstrap directory
-cd $FAST_PWD/00-bootstrap
+# move to the 0-bootstrap directory
+cd $FAST_PWD/0-bootstrap
# copy the template terraform tfvars file and save as `terraform.tfvars`
# then edit to match your environment!
edit terraform.tfvars.sample
```
+
Here you have a terraform.tfvars example:
+
```hcl
# fetch the required id by running `gcloud beta billing accounts list`
billing_account={
@@ -98,32 +114,38 @@ outputs_location = "~/fast-config"
terraform init
terraform apply -var bootstrap_user=$FAST_BU
-# link the generated provider file
-ln -s ~/fast-config/providers/00-bootstrap* .
+# link providers file
+ln -s ~/fast-config/providers/0-bootstrap-providers.tf ./
# re-run init and apply to remove user-level IAM
terraform init -migrate-state
+
# answer 'yes' to terraform's question
terraform apply
```
## Stage 1 (Resource Management)
+
This stage performs two important tasks:
+
- Create the top-level hierarchy of folders, and the associated resources used later on to automate each part of the hierarchy (eg. Networking).
- Set organization policies on the organization, and any exception required on specific folders.
+
```bash
# move to the 01-resman directory
-cd $FAST_PWD/01-resman
+cd $FAST_PWD/1-resman
-# Link providers and variables from previous stages
-ln -s ~/fast-config/providers/01-resman-providers.tf .
-ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json .
+# link providers and variables from previous stages
+ln -s ~/fast-config/providers/1-resman-providers.tf .
+ln -s ~/fast-config/tfvars/0-bootstrap.auto.tfvars.json .
ln -s ~/fast-config/tfvars/globals.auto.tfvars.json .
-# Edit your terraform.tfvars to append Teams configuration (optional)
+# edit your terraform.tfvars to append Teams configuration (optional)
edit terraform.tfvars
```
+
In the following terraform.tfvars it is shown an example of configuration for teams provisioning:
+
```hcl
outputs_location = "~/fast-config"
@@ -140,6 +162,7 @@ team_folders = {
}
}
```
+
```bash
# run init and apply
terraform init
@@ -147,28 +170,34 @@ terraform apply
```
## Stage 2 (Networking)
+
In this stage, we will deploy one of the 3 available Hub&Spoke networking topologies:
+
1. VPC Peering
2. HA VPN
3. Multi-NIC appliances (NVA)
+
```bash
# move to the 02-networking-XXX directory (where XXX should be one of vpn|peering|nva)
-cd $FAST_PWD/02-networking-XXX
+cd $FAST_PWD/2-networking-XXX
# setup providers and variables from previous stages
-ln -s ~/fast-config/providers/02-networking-providers.tf .
-ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json .
-ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json .
+ln -s ~/fast-config/providers/2-networking-providers.tf .
+ln -s ~/fast-config/tfvars/0-bootstrap.auto.tfvars.json .
+ln -s ~/fast-config/tfvars/1-resman.auto.tfvars.json .
ln -s ~/fast-config/tfvars/globals.auto.tfvars.json .
-# Create terraform.tfvars. output_location variable is required to generate networking stage output file
+# create terraform.tfvars. output_location variable is required to generate networking stage output file
edit terraform.tfvars
```
+
In the following terraform.tfvars we configure output_location variable to generate networking stage output file:
+
```hcl
# path for automatic generation of configs
outputs_location = "~/fast-config"
```
+
```bash
# run init and apply
terraform init
@@ -176,21 +205,25 @@ terraform apply
```
## Stage 2 (Security)
+
This stage sets up security resources (KMS and VPC-SC) and configurations which impact the whole organization, or are shared across the hierarchy to other projects and teams.
+
```bash
# move to the 02-security directory
cd $FAST_PWD/02-security
# link providers and variables from previous stages
-ln -s ~/fast-config/providers/02-security-providers.tf .
-ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json .
-ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json .
+ln -s ~/fast-config/providers/2-security-providers.tf .
+ln -s ~/fast-config/tfvars/0-bootstrap.auto.tfvars.json .
+ln -s ~/fast-config/tfvars/1-resman.auto.tfvars.json .
ln -s ~/fast-config/tfvars/globals.auto.tfvars.json .
-# Edit terraform.tfvars to include KMS and/or VPC-SC configuration
+# edit terraform.tfvars to include KMS and/or VPC-SC configuration
edit terraform.tfvars
```
-Some examples of terraform.tfvars configurations for KMS and VPC-SC can be found [here](02-security#customizations)
+
+Some examples of terraform.tfvars configurations for KMS and VPC-SC can be found [here](2-security#customizations)
+
```bash
# run init and apply
terraform init
@@ -198,21 +231,24 @@ terraform apply
```
## Stage 3 (Project Factory)
-The Project Factory stage builds on top of your foundations to create and set up projects (and related resources) to be used for your workloads. It is organized in folders representing environments (e.g. "dev", "prod"), each implemented by a stand-alone terraform resource factory.
-```bash
-# Variable `outputs_location` is set to `~/fast-config`
-cd $FAST_PWD/03-project-factory/ENVIRONMENT
-ln -s ~/fast-config/providers/03-project-factory-ENVIRONMENT-providers.tf .
-ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json .
-ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json .
-ln -s ~/fast-config/tfvars/02-networking.auto.tfvars.json .
+The Project Factory stage builds on top of your foundations to create and set up projects (and related resources) to be used for your workloads. It is organized in folders representing environments (e.g. "dev", "prod"), each implemented by a stand-alone terraform resource factory.
+
+```bash
+# variable `outputs_location` is set to `~/fast-config`
+cd $FAST_PWD/3-project-factory/ENVIRONMENT
+ln -s ~/fast-config/providers/3-project-factory-ENVIRONMENT-providers.tf .
+
+ln -s ~/fast-config/tfvars/0-bootstrap.auto.tfvars.json .
+ln -s ~/fast-config/tfvars/1-resman.auto.tfvars.json .
+ln -s ~/fast-config/tfvars/2-networking.auto.tfvars.json .
ln -s ~/fast-config/tfvars/globals.auto.tfvars.json .
-# Define your environment default values (eg for billing alerts and labels)
+# define your environment default values (eg for billing alerts and labels)
edit data/defaults.yaml
-# Create one yaml file per project to be created. Yaml file will include project configuration. Projects will be named after the filename
+# create one YAML file per project to be created with project configuration
+# filenames will be used for project ids
cp data/projects/project.yaml.sample data/projects/YOUR_PROJECT_NAME.yaml
edit data/projects/YOUR_PROJECT_NAME.yaml
diff --git a/fast/stages/FAQ.md b/fast/stages/FAQ.md
new file mode 100644
index 000000000..5245c8a96
--- /dev/null
+++ b/fast/stages/FAQ.md
@@ -0,0 +1,13 @@
+# FAST Mini FAQ
+
+- **How can the automation, logging and/or billing export projects be placed under specific folders instead of the org?**
+ - Run the bootstrap stage and let automation, logging and/or billing projects be created under the organization.
+ - Add the needed folders to the resource manager stage, or create them outside the stage in the console/gcloud or from a custom Terraform setup.
+ - Once folders have been created go back to the bootstrap stage, and edit your tfvars file by adding their ids to the `project_parent_ids` variable.
+ - Run the bootstrap stage again, the projects will be moved under the desired folders.
+- **Why do we need two separate service accounts when configuring CI/CD pipelines (CI/CD SA and IaC SA)?**
+ - To have the pipeline workflow follow the same impersonation flow ([CI/CD SA impersonates IaC SA](IaC_SA.png)) used when applying Terraform manually (user impersonates IaC SA), which allows the pipeline to consume the same auto-generated provider files.
+ - To allow disabling pipeline credentials in case of issues with a single operation, by removing the ability of the CI/CD SA to impersonate the IaC SA.
+- **How can I fix permission issues when running Terraform apply?**
+ - Make sure your account is part of the organization admin group defined in variables.
+ - Make sure you have configured [application default credentials](https://cloud.google.com/docs/authentication/application-default-credentials), rerun `gcloud auth login --update-adc` to fix them.
diff --git a/fast/stages/IaC_SA.png b/fast/stages/IaC_SA.png
new file mode 100644
index 000000000..8247f429e
Binary files /dev/null and b/fast/stages/IaC_SA.png differ
diff --git a/fast/stages/README.md b/fast/stages/README.md
index 9b41bf1ca..acb14be6c 100644
--- a/fast/stages/README.md
+++ b/fast/stages/README.md
@@ -1,4 +1,4 @@
-# Fast stages
+# FAST stages
Each of the folders contained here is a separate "stage", or Terraform root module.
@@ -9,7 +9,7 @@ When combined together, each stage is designed to leverage the previous stage's
This has two important consequences
- any stage can be swapped out and replaced by different code as long as it respects the contract by providing a predefined set of outputs and optionally accepting a predefined set of variables
-- data flow between stages can be partially automated (see [stage 00 documentation on output files](./00-bootstrap/README.md#output-files-and-cross-stage-variables)), reducing the effort and pain required to compile variables by hand
+- data flow between stages can be partially automated (see [stage 00 documentation on output files](./0-bootstrap/README.md#output-files-and-cross-stage-variables)), reducing the effort and pain required to compile variables by hand
One important assumption is that the flow of data is always forward looking, so no stage needs to depend on outputs generated further down the chain. This greatly simplifies both the logic and the implementation, and allows stages to be effectively independent.
@@ -19,28 +19,32 @@ Refer to each stage's documentation for a detailed description of its purpose, t
To destroy a previous FAST deployment follow the instructions detailed in [cleanup](CLEANUP.md).
-## Organizational level (00-01)
+## Organization (0 and 1)
-- [Bootstrap](00-bootstrap/README.md)
+- [Bootstrap](0-bootstrap/README.md)
Enables critical organization-level functionality that depends on broad permissions. It has two primary purposes. The first is to bootstrap the resources needed for automation of this and the following stages (service accounts, GCS buckets). And secondly, it applies the minimum amount of configuration needed at the organization level, to avoid the need of broad permissions later on, and to implement a minimum of security features like sinks and exports from the start.\
Exports: automation variables, organization-level custom roles
-- [Resource Management](01-resman/README.md)
+- [Resource Management](1-resman/README.md)
Creates the base resource hierarchy (folders) and the automation resources required later to delegate deployment of each part of the hierarchy to separate stages. This stage also configures organization-level policies and any exceptions needed by different branches of the resource hierarchy.\
Exports: folder ids, automation service account emails
-## Shared resources (02)
+## Multitenancy
-- [Security](02-security/README.md)
+Implemented via separate stages that configure separate FAST-enabled hierarchies for each tenant, check the [multitenant stages folder](../stages-multitenant/).
+
+## Shared resources (2)
+
+- [Security](2-security/README.md)
Manages centralized security configurations in a separate stage, and is typically owned by the security team. This stage implements VPC Security Controls via separate perimeters for environments and central services, and creates projects to host centralized KMS keys used by the whole organization. It's meant to be easily extended to include other security-related resources which are required, like Secret Manager.\
Exports: KMS key ids
-- Networking ([VPN](02-networking-vpn/README.md)/[NVA](02-networking-nva/README.md)/[Peering](02-networking-separate-envs/README.md)/[Separate environments](02-networking-separate-envs/README.md))
- Manages centralized network resources in a separate stage, and is typically owned by the networking team. This stage implements a hub-and-spoke design, and includes connectivity via VPN to on-premises, and YAML-based factories for firewall rules (hierarchical and VPC-level) and subnets. It's currently available in four flavors: [spokes connected via VPN](02-networking-vpn/README.md), [and spokes connected via appliances](02-networking-nva/README.md), [spokes connected via VPC peering](02-networking-peering/README.md), and [separated network environments](02-networking-separate-envs/README.md).\
+- Networking ([Peering](2-networking-a-peering/README.md)/[VPN](2-networking-b-vpn/README.md)/[NVA](2-networking-c-nva/README.md)/[Separate environments](2-networking-d-separate-envs/README.md))
+ Manages centralized network resources in a separate stage, and is typically owned by the networking team. This stage implements a hub-and-spoke design, and includes connectivity via VPN to on-premises, and YAML-based factories for firewall rules (hierarchical and VPC-level) and subnets. It's currently available in four flavors: [spokes connected via VPC peering](2-networking-a-peering/README.md), [spokes connected via VPN](2-networking-b-vpn/README.md), [and spokes connected via appliances](2-networking-c-nva/README.md), and [separated network environments](2-networking-d-separate-envs/README.md).\
Exports: host project ids and numbers, vpc self links
-## Environment-level resources (03)
+## Environment-level resources (3)
-- [Project Factory](03-project-factory/dev/)
+- [Project Factory](3-project-factory/dev/)
YAML-based fatory to create and configure application or team-level projects. Configuration includes VPC-level settings for Shared VPC, service-level configuration for CMEK encryption via centralized keys, and service account creation for workloads and applications. This stage is meant to be used once per environment.
-- [Data Platform](03-data-platform/dev/)
-- [GKE Multitenant](03-gke-multitenant/dev/)
+- [Data Platform](3-data-platform/dev/)
+- [GKE Multitenant](3-gke-multitenant/dev/)
- GCE Migration (in development)
diff --git a/modules/__experimental/net-neg/README.md b/modules/__experimental/net-neg/README.md
index a4113f0ca..cb271c505 100644
--- a/modules/__experimental/net-neg/README.md
+++ b/modules/__experimental/net-neg/README.md
@@ -7,11 +7,11 @@ Note: this module will integrated into a general-purpose load balancing module i
## Example
```hcl
module "neg" {
- source = "./fabric/modules/net-neg"
+ source = "./fabric/modules/__experimental/net-neg/"
project_id = "myproject"
name = "myneg"
- network = module.vpc.self_link
- subnetwork = module.vpc.subnet_self_links["europe-west1/default"]
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
zone = "europe-west1-b"
endpoints = [
for instance in module.vm.instances :
@@ -22,6 +22,7 @@ module "neg" {
}
]
}
+# tftest skip
```
diff --git a/modules/__experimental/net-neg/versions.tf b/modules/__experimental/net-neg/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/__experimental/net-neg/versions.tf
+++ b/modules/__experimental/net-neg/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/api-gateway/README.md b/modules/api-gateway/README.md
index 7c15f5814..d3c16d38c 100644
--- a/modules/api-gateway/README.md
+++ b/modules/api-gateway/README.md
@@ -1,4 +1,4 @@
-# Api Gateway
+# API Gateway
This module allows creating an API with its associated API config and API gateway. It also allows you grant IAM roles on the created resources.
# Examples
@@ -6,55 +6,55 @@ This module allows creating an API with its associated API config and API gatewa
## Basic example
```hcl
module "gateway" {
- source = "./fabric/modules/api-gateway"
- project_id = "my-project"
- api_id = "api"
- region = "europe-west1"
- spec = <string | ✓ | |
+| [project_id](variables.tf#L78) | Project ID. | string | ✓ | |
| [endpoint_attachments](variables.tf#L17) | Endpoint attachments. | map(object({…})) | | null |
| [envgroups](variables.tf#L26) | Environment groups (NAME => [HOSTNAMES]). | map(list(string)) | | null |
-| [environments](variables.tf#L32) | Environments. | map(object({…})) | | null |
-| [instances](variables.tf#L47) | Instances. | map(object({…})) | | null |
-| [organization](variables.tf#L61) | Apigee organization. If set to null the organization must already exist. | object({…}) | | null |
+| [environments](variables.tf#L32) | Environments. | map(object({…})) | | null |
+| [instances](variables.tf#L49) | Instances. | map(object({…})) | | null |
+| [organization](variables.tf#L64) | Apigee organization. If set to null the organization must already exist. | object({…}) | | null |
## Outputs
| name | description | sensitive |
|---|---|:---:|
-| [envgroups](outputs.tf#L17) | Environment groups. | |
-| [environments](outputs.tf#L22) | Environment. | |
-| [instances](outputs.tf#L27) | Instances. | |
-| [org_id](outputs.tf#L32) | Organization ID. | |
-| [org_name](outputs.tf#L37) | Organization name. | |
-| [organization](outputs.tf#L42) | Organization. | |
-| [service_attachments](outputs.tf#L47) | Service attachments. | |
+| [endpoint_attachment_hosts](outputs.tf#L17) | Endpoint hosts. | |
+| [envgroups](outputs.tf#L22) | Environment groups. | |
+| [environments](outputs.tf#L27) | Environment. | |
+| [instances](outputs.tf#L32) | Instances. | |
+| [org_id](outputs.tf#L37) | Organization ID. | |
+| [org_name](outputs.tf#L42) | Organization name. | |
+| [organization](outputs.tf#L47) | Organization. | |
+| [service_attachments](outputs.tf#L52) | Service attachments. | |
diff --git a/modules/apigee/main.tf b/modules/apigee/main.tf
index fe34a7382..aa2d076a2 100644
--- a/modules/apigee/main.tf
+++ b/modules/apigee/main.tf
@@ -40,10 +40,12 @@ resource "google_apigee_envgroup" "envgroups" {
}
resource "google_apigee_environment" "environments" {
- for_each = local.environments
- name = each.key
- display_name = each.value.display_name
- description = each.value.description
+ for_each = local.environments
+ name = each.key
+ display_name = each.value.display_name
+ description = each.value.description
+ deployment_type = each.value.deployment_type
+ api_proxy_type = each.value.api_proxy_type
dynamic "node_config" {
for_each = try(each.value.node_config, null) != null ? [""] : []
content {
@@ -91,7 +93,7 @@ resource "google_apigee_instance" "instances" {
description = each.value.description
location = each.value.region
org_id = local.org_id
- ip_range = each.value.psa_ip_cidr_range
+ ip_range = "${each.value.runtime_ip_cidr_range},${each.value.troubleshooting_ip_cidr_range}"
disk_encryption_key_name = each.value.disk_encryption_key
consumer_accept_list = each.value.consumer_accept_list
}
diff --git a/modules/apigee/outputs.tf b/modules/apigee/outputs.tf
index a5e703881..74ad9f18d 100644
--- a/modules/apigee/outputs.tf
+++ b/modules/apigee/outputs.tf
@@ -14,6 +14,11 @@
* limitations under the License.
*/
+output "endpoint_attachment_hosts" {
+ description = "Endpoint hosts."
+ value = { for k, v in google_apigee_endpoint_attachment.endpoint_attachments : k => v.host }
+}
+
output "envgroups" {
description = "Environment groups."
value = try(google_apigee_envgroup.envgroups, null)
diff --git a/modules/apigee/variables.tf b/modules/apigee/variables.tf
index 266f0d34e..00961aac2 100644
--- a/modules/apigee/variables.tf
+++ b/modules/apigee/variables.tf
@@ -32,8 +32,10 @@ variable "envgroups" {
variable "environments" {
description = "Environments."
type = map(object({
- display_name = optional(string)
- description = optional(string, "Terraform-managed")
+ display_name = optional(string)
+ description = optional(string, "Terraform-managed")
+ deployment_type = optional(string)
+ api_proxy_type = optional(string)
node_config = optional(object({
min_node_count = optional(number)
max_node_count = optional(number)
@@ -47,13 +49,14 @@ variable "environments" {
variable "instances" {
description = "Instances."
type = map(object({
- display_name = optional(string)
- description = optional(string, "Terraform-managed")
- region = string
- environments = list(string)
- psa_ip_cidr_range = string
- disk_encryption_key = optional(string)
- consumer_accept_list = optional(list(string))
+ display_name = optional(string)
+ description = optional(string, "Terraform-managed")
+ region = string
+ environments = list(string)
+ runtime_ip_cidr_range = string
+ troubleshooting_ip_cidr_range = string
+ disk_encryption_key = optional(string)
+ consumer_accept_list = optional(list(string))
}))
default = null
}
diff --git a/modules/apigee/versions.tf b/modules/apigee/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/apigee/versions.tf
+++ b/modules/apigee/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/artifact-registry/versions.tf b/modules/artifact-registry/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/artifact-registry/versions.tf
+++ b/modules/artifact-registry/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/bigquery-dataset/README.md b/modules/bigquery-dataset/README.md
index 29acba39c..6dbd120c2 100644
--- a/modules/bigquery-dataset/README.md
+++ b/modules/bigquery-dataset/README.md
@@ -21,7 +21,7 @@ The access variables are split into `access` and `access_identities` variables,
module "bigquery-dataset" {
source = "./fabric/modules/bigquery-dataset"
project_id = "my-project"
- id = "my-dataset"
+ id = "my-dataset"
access = {
reader-group = { role = "READER", type = "group" }
owner = { role = "OWNER", type = "user" }
@@ -46,7 +46,7 @@ Access configuration can also be specified via IAM instead of basic roles via th
module "bigquery-dataset" {
source = "./fabric/modules/bigquery-dataset"
project_id = "my-project"
- id = "my-dataset"
+ id = "my-dataset"
iam = {
"roles/bigquery.dataOwner" = ["user:user1@example.org"]
}
@@ -67,6 +67,7 @@ module "bigquery-dataset" {
default_table_expiration_ms = 3600000
default_partition_expiration_ms = null
delete_contents_on_destroy = false
+ max_time_travel_hours = 168
}
}
# tftest modules=1 resources=1
@@ -178,7 +179,7 @@ module "bigquery-dataset" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [id](variables.tf#L69) | Dataset id. | string | ✓ | |
-| [project_id](variables.tf#L100) | Id of the project where datasets will be created. | string | ✓ | |
+| [project_id](variables.tf#L97) | Id of the project where datasets will be created. | string | ✓ | |
| [access](variables.tf#L17) | Map of access rules with role and identity type. Keys are arbitrary and must match those in the `access_identities` variable, types are `domain`, `group`, `special_group`, `user`, `view`. | map(object({…})) | | {} |
| [access_identities](variables.tf#L33) | Map of access identities used for basic access roles. View identities have the format 'project_id\|dataset_id\|table_id'. | map(string) | | {} |
| [dataset_access](variables.tf#L39) | Set access in the dataset resource instead of using separate resources. | bool | | false |
@@ -188,9 +189,9 @@ module "bigquery-dataset" {
| [iam](variables.tf#L63) | IAM bindings in {ROLE => [MEMBERS]} format. Mutually exclusive with the access_* variables used for basic roles. | map(list(string)) | | {} |
| [labels](variables.tf#L74) | Dataset labels. | map(string) | | {} |
| [location](variables.tf#L80) | Dataset location. | string | | "EU" |
-| [options](variables.tf#L86) | Dataset options. | object({…}) | | {…} |
-| [tables](variables.tf#L105) | Table definitions. Options and partitioning default to null. Partitioning can only use `range` or `time`, set the unused one to null. | map(object({…})) | | {} |
-| [views](variables.tf#L133) | View definitions. | map(object({…})) | | {} |
+| [options](variables.tf#L86) | Dataset options. | object({…}) | | {} |
+| [tables](variables.tf#L102) | Table definitions. Options and partitioning default to null. Partitioning can only use `range` or `time`, set the unused one to null. | map(object({…})) | | {} |
+| [views](variables.tf#L130) | View definitions. | map(object({…})) | | {} |
## Outputs
diff --git a/modules/bigquery-dataset/main.tf b/modules/bigquery-dataset/main.tf
index 47f8fcb53..f832cd85c 100644
--- a/modules/bigquery-dataset/main.tf
+++ b/modules/bigquery-dataset/main.tf
@@ -42,7 +42,7 @@ resource "google_bigquery_dataset" "default" {
delete_contents_on_destroy = var.options.delete_contents_on_destroy
default_table_expiration_ms = var.options.default_table_expiration_ms
default_partition_expiration_ms = var.options.default_partition_expiration_ms
-
+ max_time_travel_hours = var.options.max_time_travel_hours
dynamic "access" {
for_each = var.dataset_access ? local.access_domain : {}
content {
diff --git a/modules/bigquery-dataset/variables.tf b/modules/bigquery-dataset/variables.tf
index 5f8028abf..b44b66585 100644
--- a/modules/bigquery-dataset/variables.tf
+++ b/modules/bigquery-dataset/variables.tf
@@ -86,15 +86,12 @@ variable "location" {
variable "options" {
description = "Dataset options."
type = object({
- default_table_expiration_ms = number
- default_partition_expiration_ms = number
- delete_contents_on_destroy = bool
+ default_table_expiration_ms = optional(number, null)
+ default_partition_expiration_ms = optional(number, null)
+ delete_contents_on_destroy = optional(bool, false)
+ max_time_travel_hours = optional(number, 168)
})
- default = {
- default_table_expiration_ms = null
- default_partition_expiration_ms = null
- delete_contents_on_destroy = false
- }
+ default = {}
}
variable "project_id" {
diff --git a/modules/bigquery-dataset/versions.tf b/modules/bigquery-dataset/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/bigquery-dataset/versions.tf
+++ b/modules/bigquery-dataset/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/bigtable-instance/README.md b/modules/bigtable-instance/README.md
index 1e9ade9c9..39e0bc536 100644
--- a/modules/bigtable-instance/README.md
+++ b/modules/bigtable-instance/README.md
@@ -4,9 +4,7 @@ This module allows managing a single BigTable instance, including access configu
## TODO
-- [ ] support bigtable_gc_policy
- [ ] support bigtable_app_profile
-- [ ] support cluster replicas
- [ ] support IAM for tables
## Examples
@@ -16,25 +14,147 @@ This module allows managing a single BigTable instance, including access configu
```hcl
module "bigtable-instance" {
- source = "./fabric/modules/bigtable-instance"
- project_id = "my-project"
- name = "instance"
- cluster_id = "instance"
- zone = "europe-west1-b"
- tables = {
- test1 = null,
- test2 = {
- split_keys = ["a", "b", "c"]
- column_family = null
+ source = "./fabric/modules/bigtable-instance"
+ project_id = "my-project"
+ name = "instance"
+ clusters = {
+ my-cluster = {
+ zone = "europe-west1-b"
}
}
- iam = {
+ tables = {
+ test1 = {},
+ test2 = {
+ split_keys = ["a", "b", "c"]
+ }
+ }
+ iam = {
"roles/bigtable.user" = ["user:viewer@testdomain.com"]
}
}
# tftest modules=1 resources=4
```
+### Instance with tables and column families
+
+```hcl
+
+module "bigtable-instance" {
+ source = "./fabric/modules/bigtable-instance"
+ project_id = "my-project"
+ name = "instance"
+ clusters = {
+ my-cluster = {
+ zone = "europe-west1-b"
+ }
+ }
+ tables = {
+ test1 = {},
+ test2 = {
+ split_keys = ["a", "b", "c"]
+ column_families = {
+ cf1 = {}
+ cf2 = {}
+ cf3 = {}
+ }
+ }
+ test3 = {
+ column_families = {
+ cf1 = {}
+ }
+ }
+ }
+}
+# tftest modules=1 resources=4
+```
+
+### Instance with replication enabled
+
+```hcl
+
+module "bigtable-instance" {
+ source = "./fabric/modules/bigtable-instance"
+ project_id = "my-project"
+ name = "instance"
+ clusters = {
+ first-cluster = {
+ zone = "europe-west1-b"
+ }
+ second-cluster = {
+ zone = "europe-southwest1-a"
+ }
+ third-cluster = {
+ zone = "us-central1-b"
+ }
+ }
+}
+# tftest modules=1 resources=1
+```
+
+### Instance with garbage collection policy
+
+```hcl
+
+module "bigtable-instance" {
+ source = "./fabric/modules/bigtable-instance"
+ project_id = "my-project"
+ name = "instance"
+ clusters = {
+ my-cluster = {
+ zone = "europe-west1-b"
+ }
+ }
+ tables = {
+ test1 = {
+ column_families = {
+ cf1 = {
+ gc_policy = {
+ deletion_policy = "ABANDON"
+ max_age = "18h"
+ }
+ }
+ cf2 = {}
+ }
+ }
+ }
+}
+# tftest modules=1 resources=3
+```
+
+### Instance with default garbage collection policy
+
+The default garbage collection policy is applied to any column family that does
+not specify a `gc_policy`. If a column family specifies a `gc_policy`, the
+default garbage collection policy is ignored for that column family.
+
+```hcl
+
+module "bigtable-instance" {
+ source = "./fabric/modules/bigtable-instance"
+ project_id = "my-project"
+ name = "instance"
+ clusters = {
+ my-cluster = {
+ zone = "europe-west1-b"
+ }
+ }
+ default_gc_policy = {
+ deletion_policy = "ABANDON"
+ max_age = "18h"
+ max_version = 7
+ }
+ tables = {
+ test1 = {
+ column_families = {
+ cf1 = {}
+ cf2 = {}
+ }
+ }
+ }
+}
+# tftest modules=1 resources=4
+```
+
### Instance with static number of nodes
If you are not using autoscaling settings, you must set a specific number of nodes with the variable `num_nodes`.
@@ -45,9 +165,12 @@ module "bigtable-instance" {
source = "./fabric/modules/bigtable-instance"
project_id = "my-project"
name = "instance"
- cluster_id = "instance"
- zone = "europe-west1-b"
- num_nodes = 5
+ clusters = {
+ my-cluster = {
+ zone = "europe-west1-b"
+ num_nodes = 5
+ }
+ }
}
# tftest modules=1 resources=1
```
@@ -59,16 +182,21 @@ If you use autoscaling, you should not set the variable `num_nodes`.
```hcl
module "bigtable-instance" {
- source = "./fabric/modules/bigtable-instance"
- project_id = "my-project"
- name = "instance"
- cluster_id = "instance"
- zone = "europe-southwest1-b"
- autoscaling_config = {
- min_nodes = 3
- max_nodes = 7
- cpu_target = 70
+ source = "./fabric/modules/bigtable-instance"
+ project_id = "my-project"
+ name = "instance"
+ clusters = {
+ my-cluster = {
+ zone = "europe-southwest1-b"
+ autoscaling = {
+ min_nodes = 3
+ max_nodes = 7
+ cpu_target = 70
+ }
+ }
}
+
+
}
# tftest modules=1 resources=1
```
@@ -78,17 +206,20 @@ module "bigtable-instance" {
```hcl
module "bigtable-instance" {
- source = "./fabric/modules/bigtable-instance"
- project_id = "my-project"
- name = "instance"
- cluster_id = "instance"
- zone = "europe-southwest1-a"
- storage_type = "SSD"
- autoscaling_config = {
- min_nodes = 3
- max_nodes = 7
- cpu_target = 70
- storage_target = 4096
+ source = "./fabric/modules/bigtable-instance"
+ project_id = "my-project"
+ name = "instance"
+ clusters = {
+ my-cluster = {
+ zone = "europe-southwest1-a"
+ storage_type = "SSD"
+ autoscaling = {
+ min_nodes = 3
+ max_nodes = 7
+ cpu_target = 70
+ storage_target = 4096
+ }
+ }
}
}
# tftest modules=1 resources=1
@@ -99,19 +230,16 @@ module "bigtable-instance" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L56) | The name of the Cloud Bigtable instance. | string | ✓ | |
-| [project_id](variables.tf#L67) | Id of the project where datasets will be created. | string | ✓ | |
-| [zone](variables.tf#L99) | The zone to create the Cloud Bigtable cluster in. | string | ✓ | |
-| [autoscaling_config](variables.tf#L17) | Settings for autoscaling of the instance. If you set this variable, the variable num_nodes is ignored. | object({…}) | | null |
-| [cluster_id](variables.tf#L28) | The ID of the Cloud Bigtable cluster. | string | | "europe-west1" |
-| [deletion_protection](variables.tf#L34) | Whether or not to allow Terraform to destroy the instance. Unless this field is set to false in Terraform state, a terraform destroy or terraform apply that would delete the instance will fail. | | | true |
-| [display_name](variables.tf#L39) | The human-readable display name of the Bigtable instance. | | | null |
-| [iam](variables.tf#L44) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
-| [instance_type](variables.tf#L50) | (deprecated) The instance type to create. One of 'DEVELOPMENT' or 'PRODUCTION'. | string | | null |
-| [num_nodes](variables.tf#L61) | The number of nodes in your Cloud Bigtable cluster. This value is ignored if you are using autoscaling. | number | | 1 |
-| [storage_type](variables.tf#L72) | The storage type to use. | string | | "SSD" |
-| [table_options_defaults](variables.tf#L78) | Default option of tables created in the BigTable instance. | object({…}) | | {…} |
-| [tables](variables.tf#L90) | Tables to be created in the BigTable instance, options can be null. | map(object({…})) | | {} |
+| [clusters](variables.tf#L17) | Clusters to be created in the BigTable instance. Set more than one cluster to enable replication. If you set autoscaling, num_nodes will be ignored. | map(object({…})) | ✓ | |
+| [name](variables.tf#L78) | The name of the Cloud Bigtable instance. | string | ✓ | |
+| [project_id](variables.tf#L83) | Id of the project where datasets will be created. | string | ✓ | |
+| [default_autoscaling](variables.tf#L33) | Default settings for autoscaling of clusters. This will be the default autoscaling for any cluster not specifying any autoscaling details. | object({…}) | | null |
+| [default_gc_policy](variables.tf#L44) | Default garbage collection policy, to be applied to all column families and all tables. Can be override in the tables variable for specific column families. | object({…}) | | null |
+| [deletion_protection](variables.tf#L56) | Whether or not to allow Terraform to destroy the instance. Unless this field is set to false in Terraform state, a terraform destroy or terraform apply that would delete the instance will fail. | | | true |
+| [display_name](variables.tf#L61) | The human-readable display name of the Bigtable instance. | | | null |
+| [iam](variables.tf#L66) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
+| [instance_type](variables.tf#L72) | (deprecated) The instance type to create. One of 'DEVELOPMENT' or 'PRODUCTION'. | string | | null |
+| [tables](variables.tf#L88) | Tables to be created in the BigTable instance. | map(object({…})) | | {} |
## Outputs
diff --git a/modules/bigtable-instance/main.tf b/modules/bigtable-instance/main.tf
index b5764c349..3a00eab89 100644
--- a/modules/bigtable-instance/main.tf
+++ b/modules/bigtable-instance/main.tf
@@ -15,34 +15,53 @@
*/
locals {
- tables = {
- for k, v in var.tables : k => v != null ? v : var.table_options_defaults
+ gc_pairs = flatten([
+ for table, table_obj in var.tables : [
+ for cf, cf_obj in table_obj.column_families : {
+ table = table
+ column_family = cf
+ gc_policy = cf_obj.gc_policy == null ? var.default_gc_policy : cf_obj.gc_policy
+ }
+ ]
+ ])
+
+ clusters_autoscaling = {
+ for cluster_id, cluster in var.clusters : cluster_id => {
+ zone = cluster.zone
+ storage_type = cluster.storage_type
+ num_nodes = cluster.autoscaling == null && var.default_autoscaling == null ? cluster.num_nodes : null
+ autoscaling = cluster.autoscaling == null ? var.default_autoscaling : cluster.autoscaling
+ }
}
- num_nodes = var.autoscaling_config == null ? var.num_nodes : null
}
resource "google_bigtable_instance" "default" {
project = var.project_id
name = var.name
- cluster {
- cluster_id = var.cluster_id
- zone = var.zone
- storage_type = var.storage_type
- num_nodes = local.num_nodes
- dynamic "autoscaling_config" {
- for_each = var.autoscaling_config == null ? [] : [""]
- content {
- min_nodes = var.autoscaling_config.min_nodes
- max_nodes = var.autoscaling_config.max_nodes
- cpu_target = var.autoscaling_config.cpu_target
- storage_target = var.autoscaling_config.storage_target
+
+ instance_type = var.instance_type
+ display_name = var.display_name == null ? var.display_name : var.name
+ deletion_protection = var.deletion_protection
+
+ dynamic "cluster" {
+ for_each = local.clusters_autoscaling
+ content {
+ cluster_id = cluster.key
+ zone = cluster.value.zone
+ storage_type = cluster.value.storage_type
+ num_nodes = cluster.value.num_nodes
+
+ dynamic "autoscaling_config" {
+ for_each = cluster.value.autoscaling == null ? [] : [""]
+ content {
+ min_nodes = cluster.value.autoscaling.min_nodes
+ max_nodes = cluster.value.autoscaling.max_nodes
+ cpu_target = cluster.value.autoscaling.cpu_target
+ storage_target = cluster.value.autoscaling.storage_target
+ }
}
}
}
- instance_type = var.instance_type
-
- display_name = var.display_name == null ? var.display_name : var.name
- deletion_protection = var.deletion_protection
}
resource "google_bigtable_instance_iam_binding" "default" {
@@ -54,17 +73,44 @@ resource "google_bigtable_instance_iam_binding" "default" {
}
resource "google_bigtable_table" "default" {
- for_each = local.tables
+ for_each = var.tables
project = var.project_id
instance_name = google_bigtable_instance.default.name
name = each.key
split_keys = each.value.split_keys
dynamic "column_family" {
- for_each = each.value.column_family != null ? [""] : []
+ for_each = each.value.column_families
content {
- family = each.value.column_family
+ family = column_family.key
+ }
+ }
+}
+
+resource "google_bigtable_gc_policy" "default" {
+ for_each = { for k, v in local.gc_pairs : k => v if v.gc_policy != null }
+
+ table = each.value.table
+ column_family = each.value.column_family
+ instance_name = google_bigtable_instance.default.name
+ project = var.project_id
+
+ gc_rules = try(each.value.gc_policy.gc_rules, null)
+ mode = try(each.value.gc_policy.mode, null)
+ deletion_policy = try(each.value.gc_policy.deletion_policy, null)
+
+ dynamic "max_age" {
+ for_each = try(each.value.gc_policy.max_age, null) != null ? [""] : []
+ content {
+ duration = each.value.gc_policy.max_age
+ }
+ }
+
+ dynamic "max_version" {
+ for_each = try(each.value.gc_policy.max_version, null) != null ? [""] : []
+ content {
+ number = each.value.gc_policy.max_version
}
}
}
diff --git a/modules/bigtable-instance/variables.tf b/modules/bigtable-instance/variables.tf
index 84a6013b6..f7b75c135 100644
--- a/modules/bigtable-instance/variables.tf
+++ b/modules/bigtable-instance/variables.tf
@@ -14,21 +14,43 @@
* limitations under the License.
*/
-variable "autoscaling_config" {
- description = "Settings for autoscaling of the instance. If you set this variable, the variable num_nodes is ignored."
+variable "clusters" {
+ description = "Clusters to be created in the BigTable instance. Set more than one cluster to enable replication. If you set autoscaling, num_nodes will be ignored."
+ nullable = false
+ type = map(object({
+ zone = optional(string)
+ storage_type = optional(string)
+ num_nodes = optional(number)
+ autoscaling = optional(object({
+ min_nodes = number
+ max_nodes = number
+ cpu_target = number
+ storage_target = optional(number)
+ }))
+ }))
+}
+
+variable "default_autoscaling" {
+ description = "Default settings for autoscaling of clusters. This will be the default autoscaling for any cluster not specifying any autoscaling details."
type = object({
min_nodes = number
max_nodes = number
- cpu_target = number,
- storage_target = optional(number, null)
+ cpu_target = number
+ storage_target = optional(number)
})
default = null
}
-variable "cluster_id" {
- description = "The ID of the Cloud Bigtable cluster."
- type = string
- default = "europe-west1"
+variable "default_gc_policy" {
+ description = "Default garbage collection policy, to be applied to all column families and all tables. Can be override in the tables variable for specific column families."
+ type = object({
+ deletion_policy = optional(string)
+ gc_rules = optional(string)
+ mode = optional(string)
+ max_age = optional(string)
+ max_version = optional(string)
+ })
+ default = null
}
variable "deletion_protection" {
@@ -58,45 +80,26 @@ variable "name" {
type = string
}
-variable "num_nodes" {
- description = "The number of nodes in your Cloud Bigtable cluster. This value is ignored if you are using autoscaling."
- type = number
- default = 1
-}
-
variable "project_id" {
description = "Id of the project where datasets will be created."
type = string
}
-variable "storage_type" {
- description = "The storage type to use."
- type = string
- default = "SSD"
-}
-
-variable "table_options_defaults" {
- description = "Default option of tables created in the BigTable instance."
- type = object({
- split_keys = list(string)
- column_family = string
- })
- default = {
- split_keys = []
- column_family = null
- }
-}
-
variable "tables" {
- description = "Tables to be created in the BigTable instance, options can be null."
+ description = "Tables to be created in the BigTable instance."
+ nullable = false
type = map(object({
- split_keys = list(string)
- column_family = string
+ split_keys = optional(list(string), [])
+ column_families = optional(map(object(
+ {
+ gc_policy = optional(object({
+ deletion_policy = optional(string)
+ gc_rules = optional(string)
+ mode = optional(string)
+ max_age = optional(string)
+ max_version = optional(string)
+ }), null)
+ })), {})
}))
default = {}
}
-
-variable "zone" {
- description = "The zone to create the Cloud Bigtable cluster in."
- type = string
-}
diff --git a/modules/bigtable-instance/versions.tf b/modules/bigtable-instance/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/bigtable-instance/versions.tf
+++ b/modules/bigtable-instance/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/billing-budget/README.md b/modules/billing-budget/README.md
index 44231d049..d995c0e0a 100644
--- a/modules/billing-budget/README.md
+++ b/modules/billing-budget/README.md
@@ -29,7 +29,7 @@ module "budget" {
]
email_recipients = {
project_id = "my-project"
- emails = ["user@example.com"]
+ emails = ["user@example.com"]
}
}
# tftest modules=1 resources=2
diff --git a/modules/billing-budget/versions.tf b/modules/billing-budget/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/billing-budget/versions.tf
+++ b/modules/billing-budget/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/binauthz/README.md b/modules/binauthz/README.md
index fa0cd71ba..960a7da31 100644
--- a/modules/binauthz/README.md
+++ b/modules/binauthz/README.md
@@ -8,8 +8,8 @@ This module simplifies the creation of a Binary Authorization policy, attestors
```hcl
module "binauthz" {
- source = "./fabric/modules/binauthz"
- project_id = "my_project"
+ source = "./fabric/modules/binauthz"
+ project_id = "my_project"
global_policy_evaluation_mode = "DISABLE"
default_admission_rule = {
evaluation_mode = "ALWAYS_DENY"
@@ -18,16 +18,16 @@ module "binauthz" {
}
cluster_admission_rules = {
"europe-west1-c.cluster" = {
- evaluation_mode = "REQUIRE_ATTESTATION"
- enforcement_mode = "ENFORCED_BLOCK_AND_AUDIT_LOG"
- attestors = [ "test" ]
+ evaluation_mode = "REQUIRE_ATTESTATION"
+ enforcement_mode = "ENFORCED_BLOCK_AND_AUDIT_LOG"
+ attestors = ["test"]
}
}
attestors_config = {
- "test": {
- note_reference = null
- pgp_public_keys = [
- <object({…}) | ✓ | |
+| [vpn_config](variables.tf#L35) | VPN configuration, type must be one of 'dynamic' or 'static'. | object({…}) | ✓ | |
| [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,7 +84,5 @@ 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/onprem/cloud-config.yaml b/modules/cloud-config-container/__need_fixing/onprem/cloud-config.yaml
similarity index 100%
rename from modules/cloud-config-container/onprem/cloud-config.yaml
rename to modules/cloud-config-container/__need_fixing/onprem/cloud-config.yaml
diff --git a/modules/cloud-config-container/onprem/docker-images/README.md b/modules/cloud-config-container/__need_fixing/onprem/docker-images/README.md
similarity index 100%
rename from modules/cloud-config-container/onprem/docker-images/README.md
rename to modules/cloud-config-container/__need_fixing/onprem/docker-images/README.md
diff --git a/modules/cloud-config-container/onprem/docker-images/strongswan/Dockerfile b/modules/cloud-config-container/__need_fixing/onprem/docker-images/strongswan/Dockerfile
similarity index 100%
rename from modules/cloud-config-container/onprem/docker-images/strongswan/Dockerfile
rename to modules/cloud-config-container/__need_fixing/onprem/docker-images/strongswan/Dockerfile
diff --git a/modules/cloud-config-container/onprem/docker-images/strongswan/README.md b/modules/cloud-config-container/__need_fixing/onprem/docker-images/strongswan/README.md
similarity index 100%
rename from modules/cloud-config-container/onprem/docker-images/strongswan/README.md
rename to modules/cloud-config-container/__need_fixing/onprem/docker-images/strongswan/README.md
diff --git a/modules/cloud-config-container/onprem/docker-images/strongswan/cloudbuild.yaml b/modules/cloud-config-container/__need_fixing/onprem/docker-images/strongswan/cloudbuild.yaml
similarity index 100%
rename from modules/cloud-config-container/onprem/docker-images/strongswan/cloudbuild.yaml
rename to modules/cloud-config-container/__need_fixing/onprem/docker-images/strongswan/cloudbuild.yaml
diff --git a/modules/cloud-config-container/onprem/docker-images/strongswan/entrypoint.sh b/modules/cloud-config-container/__need_fixing/onprem/docker-images/strongswan/entrypoint.sh
similarity index 100%
rename from modules/cloud-config-container/onprem/docker-images/strongswan/entrypoint.sh
rename to modules/cloud-config-container/__need_fixing/onprem/docker-images/strongswan/entrypoint.sh
diff --git a/modules/cloud-config-container/onprem/docker-images/strongswan/ipsec-vti.sh b/modules/cloud-config-container/__need_fixing/onprem/docker-images/strongswan/ipsec-vti.sh
similarity index 100%
rename from modules/cloud-config-container/onprem/docker-images/strongswan/ipsec-vti.sh
rename to modules/cloud-config-container/__need_fixing/onprem/docker-images/strongswan/ipsec-vti.sh
diff --git a/modules/cloud-config-container/onprem/docker-images/toolbox/Dockerfile b/modules/cloud-config-container/__need_fixing/onprem/docker-images/toolbox/Dockerfile
similarity index 100%
rename from modules/cloud-config-container/onprem/docker-images/toolbox/Dockerfile
rename to modules/cloud-config-container/__need_fixing/onprem/docker-images/toolbox/Dockerfile
diff --git a/modules/cloud-config-container/onprem/docker-images/toolbox/README.md b/modules/cloud-config-container/__need_fixing/onprem/docker-images/toolbox/README.md
similarity index 100%
rename from modules/cloud-config-container/onprem/docker-images/toolbox/README.md
rename to modules/cloud-config-container/__need_fixing/onprem/docker-images/toolbox/README.md
diff --git a/modules/cloud-config-container/onprem/docker-images/toolbox/cloudbuild.yaml b/modules/cloud-config-container/__need_fixing/onprem/docker-images/toolbox/cloudbuild.yaml
similarity index 100%
rename from modules/cloud-config-container/onprem/docker-images/toolbox/cloudbuild.yaml
rename to modules/cloud-config-container/__need_fixing/onprem/docker-images/toolbox/cloudbuild.yaml
diff --git a/modules/cloud-config-container/onprem/docker-images/toolbox/entrypoint.sh b/modules/cloud-config-container/__need_fixing/onprem/docker-images/toolbox/entrypoint.sh
similarity index 100%
rename from modules/cloud-config-container/onprem/docker-images/toolbox/entrypoint.sh
rename to modules/cloud-config-container/__need_fixing/onprem/docker-images/toolbox/entrypoint.sh
diff --git a/modules/cloud-config-container/onprem/main.tf b/modules/cloud-config-container/__need_fixing/onprem/main.tf
similarity index 100%
rename from modules/cloud-config-container/onprem/main.tf
rename to modules/cloud-config-container/__need_fixing/onprem/main.tf
diff --git a/modules/cloud-config-container/onprem/outputs.tf b/modules/cloud-config-container/__need_fixing/onprem/outputs.tf
similarity index 100%
rename from modules/cloud-config-container/onprem/outputs.tf
rename to modules/cloud-config-container/__need_fixing/onprem/outputs.tf
diff --git a/modules/cloud-config-container/onprem/static-vpn-gw-cloud-init.yaml b/modules/cloud-config-container/__need_fixing/onprem/static-vpn-gw-cloud-init.yaml
similarity index 100%
rename from modules/cloud-config-container/onprem/static-vpn-gw-cloud-init.yaml
rename to modules/cloud-config-container/__need_fixing/onprem/static-vpn-gw-cloud-init.yaml
diff --git a/modules/cloud-config-container/onprem/variables.tf b/modules/cloud-config-container/__need_fixing/onprem/variables.tf
similarity index 94%
rename from modules/cloud-config-container/onprem/variables.tf
rename to modules/cloud-config-container/__need_fixing/onprem/variables.tf
index 3b09e2366..06eb27603 100644
--- a/modules/cloud-config-container/onprem/variables.tf
+++ b/modules/cloud-config-container/__need_fixing/onprem/variables.tf
@@ -37,9 +37,9 @@ variable "vpn_config" {
type = object({
peer_ip = string
shared_secret = string
- type = string
- peer_ip2 = string
- shared_secret2 = string
+ type = optional(string, "static")
+ peer_ip2 = optional(string)
+ shared_secret2 = optional(string)
})
}
diff --git a/modules/cloud-config-container/__need_fixing/onprem/versions.tf b/modules/cloud-config-container/__need_fixing/onprem/versions.tf
new file mode 100644
index 000000000..08492c6f9
--- /dev/null
+++ b/modules/cloud-config-container/__need_fixing/onprem/versions.tf
@@ -0,0 +1,29 @@
+# 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
+#
+# https://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.
+
+terraform {
+ required_version = ">= 1.3.1"
+ required_providers {
+ google = {
+ source = "hashicorp/google"
+ version = ">= 4.50.0" # tftest
+ }
+ google-beta = {
+ source = "hashicorp/google-beta"
+ version = ">= 4.50.0" # tftest
+ }
+ }
+}
+
+
diff --git a/modules/cloud-config-container/coredns/README.md b/modules/cloud-config-container/coredns/README.md
index f5a51a389..4abe69e32 100644
--- a/modules/cloud-config-container/coredns/README.md
+++ b/modules/cloud-config-container/coredns/README.md
@@ -24,17 +24,30 @@ This example will create a `cloud-config` that uses the module's defaults, creat
```hcl
module "cos-coredns" {
- source = "./fabric/modules/cloud-config-container/coredns"
+ source = "./fabric/modules/cloud-config-container/coredns"
}
-# use it as metadata in a compute instance or template
-module "vm-coredns" {
- source = "./fabric/modules/compute-vm"
+module "vm" {
+ source = "./fabric/modules/compute-vm"
+ project_id = "my-project"
+ zone = "europe-west8-b"
+ name = "cos-coredns"
+ network_interfaces = [{
+ network = "default"
+ subnetwork = "gce"
+ }]
metadata = {
user-data = module.cos-coredns.cloud_config
google-logging-enabled = true
}
+ boot_disk = {
+ image = "projects/cos-cloud/global/images/family/cos-stable"
+ type = "pd-ssd"
+ size = 10
+ }
+ tags = ["dns", "ssh"]
}
+# tftest modules=1 resources=1
```
### Custom CoreDNS configuration
@@ -43,7 +56,7 @@ This example will create a `cloud-config` using a custom CoreDNS configuration,
```hcl
module "cos-coredns" {
- source = "./fabric/modules/cloud-config-container/coredns"
+ source = "./fabric/modules/cloud-config-container/coredns"
coredns_config = "./fabric/modules/cloud-config-container/coredns/Corefile-hosts"
files = {
"/etc/coredns/example.hosts" = {
@@ -51,25 +64,9 @@ module "cos-coredns" {
owner = null
permissions = "0644"
}
-}
-```
-
-### CoreDNS instance
-
-This example shows how to create the single instance optionally managed by the module, providing all required attributes in the `test_instance` variable. The instance is purposefully kept simple and should only be used in development, or when designing infrastructures.
-
-```hcl
-module "cos-coredns" {
- source = "./fabric/modules/cloud-config-container/coredns"
- test_instance = {
- project_id = "my-project"
- zone = "europe-west1-b"
- name = "cos-coredns"
- type = "f1-micro"
- network = "default"
- subnetwork = "https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west1/subnetworks/my-subnet"
}
}
+# tftest modules=0 resources=0
```
@@ -82,14 +79,11 @@ 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/coredns/instance.tf b/modules/cloud-config-container/coredns/instance.tf
deleted file mode 120000
index bdef596b6..000000000
--- a/modules/cloud-config-container/coredns/instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/coredns/outputs-instance.tf b/modules/cloud-config-container/coredns/outputs-instance.tf
deleted file mode 120000
index ea9e24045..000000000
--- a/modules/cloud-config-container/coredns/outputs-instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../outputs-instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/coredns/variables-instance.tf b/modules/cloud-config-container/coredns/variables-instance.tf
deleted file mode 120000
index 94af61e4d..000000000
--- a/modules/cloud-config-container/coredns/variables-instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../variables-instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/coredns/versions.tf b/modules/cloud-config-container/coredns/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/cloud-config-container/coredns/versions.tf
+++ b/modules/cloud-config-container/coredns/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/cos-generic-metadata/README.md b/modules/cloud-config-container/cos-generic-metadata/README.md
index 16d1935ed..88073986d 100644
--- a/modules/cloud-config-container/cos-generic-metadata/README.md
+++ b/modules/cloud-config-container/cos-generic-metadata/README.md
@@ -12,31 +12,27 @@ This example will create a `cloud-config` that starts [Envoy Proxy](https://www.
```hcl
module "cos-envoy" {
- source = "./fabric/modules/cos-generic-metadata"
-
+ source = "./fabric/modules/cloud-config-container/cos-generic-metadata"
container_image = "envoyproxy/envoy:v1.14.1"
container_name = "envoy"
container_args = "-c /etc/envoy/envoy.yaml --log-level info --allow-unknown-static-fields"
-
container_volumes = [
{ host = "/etc/envoy/envoy.yaml", container = "/etc/envoy/envoy.yaml" }
]
-
docker_args = "--network host --pid host"
-
+ # file paths are mocked to run this example in tests
files = {
"/var/run/envoy/customize.sh" = {
- content = file("customize.sh")
+ content = file("/dev/null") # file("customize.sh")
owner = "root"
permissions = "0744"
}
"/etc/envoy/envoy.yaml" = {
- content = file("envoy.yaml")
+ content = file("/dev/null") # file("envoy.yaml")
owner = "root"
permissions = "0644"
}
}
-
run_commands = [
"iptables -t nat -N ENVOY_IN_REDIRECT",
"iptables -t nat -A ENVOY_IN_REDIRECT -p tcp -j REDIRECT --to-port 15001",
@@ -46,14 +42,13 @@ module "cos-envoy" {
"systemctl daemon-reload",
"systemctl start envoy",
]
-
- users = [
- {
- username = "envoy",
- uid = 1337
- }
- ]
+ users = [{
+ username = "envoy",
+ uid = 1337
+ }]
}
+
+# tftest modules=0 resources=0
```
diff --git a/modules/cloud-config-container/cos-generic-metadata/versions.tf b/modules/cloud-config-container/cos-generic-metadata/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/cloud-config-container/cos-generic-metadata/versions.tf
+++ b/modules/cloud-config-container/cos-generic-metadata/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/envoy-traffic-director/README.md b/modules/cloud-config-container/envoy-traffic-director/README.md
index 593cfa83c..caa0ec5ec 100644
--- a/modules/cloud-config-container/envoy-traffic-director/README.md
+++ b/modules/cloud-config-container/envoy-traffic-director/README.md
@@ -11,38 +11,31 @@ This module depends on the [`cos-generic-metadata` module](../cos-generic-metada
### Default configuration
```hcl
-# Envoy TD config
module "cos-envoy-td" {
source = "./fabric/modules/cloud-config-container/envoy-traffic-director"
}
-# COS VM
-module "vm-cos" {
+module "vm" {
source = "./fabric/modules/compute-vm"
- project_id = local.project_id
- zone = local.zone
+ project_id = "my-project"
+ zone = "europe-west8-b"
name = "cos-envoy-td"
network_interfaces = [{
- network = local.vpc.self_link,
- subnetwork = local.vpc.subnet_self_link,
- nat = false,
- addresses = null
+ network = "default"
+ subnetwork = "gce"
}]
- tags = ["ssh", "http"]
-
metadata = {
user-data = module.cos-envoy-td.cloud_config
google-logging-enabled = true
}
-
boot_disk = {
image = "projects/cos-cloud/global/images/family/cos-stable"
type = "pd-ssd"
size = 10
}
-
- service_account_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
+ tags = ["http-server", "ssh"]
}
+# tftest modules=1 resources=1
```
diff --git a/modules/cloud-config-container/envoy-traffic-director/versions.tf b/modules/cloud-config-container/envoy-traffic-director/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/cloud-config-container/envoy-traffic-director/versions.tf
+++ b/modules/cloud-config-container/envoy-traffic-director/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/instance.tf b/modules/cloud-config-container/instance.tf
deleted file mode 100644
index 7f76f2183..000000000
--- a/modules/cloud-config-container/instance.tf
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * 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.
- */
-
-resource "google_service_account" "default" {
- count = var.test_instance == null ? 0 : 1
- project = var.test_instance.project_id
- account_id = "fabric-container-${var.test_instance.name}"
- display_name = "Managed by the cos Terraform module."
-}
-
-resource "google_project_iam_member" "default" {
- for_each = (
- var.test_instance == null
- ? toset([])
- : toset(var.test_instance_defaults.service_account_roles)
- )
- project = var.test_instance.project_id
- role = each.value
- member = "serviceAccount:${google_service_account.default[0].email}"
-}
-
-resource "google_compute_disk" "disks" {
- for_each = (
- var.test_instance == null
- ? {}
- : var.test_instance_defaults.disks
- )
- project = var.test_instance.project_id
- zone = var.test_instance.zone
- name = each.key
- type = "pd-ssd"
- size = each.value.size
-}
-
-resource "google_compute_instance" "default" {
- count = var.test_instance == null ? 0 : 1
- project = var.test_instance.project_id
- zone = var.test_instance.zone
- name = var.test_instance.name
- description = "Managed by the cos Terraform module."
- tags = var.test_instance_defaults.tags
- machine_type = (
- var.test_instance.type == null ? "f1-micro" : var.test_instance.type
- )
- metadata = merge(var.test_instance_defaults.metadata, {
- user-data = local.cloud_config
- })
-
- dynamic "attached_disk" {
- for_each = var.test_instance_defaults.disks
- iterator = disk
- content {
- device_name = disk.key
- mode = disk.value.read_only ? "READ_ONLY" : "READ_WRITE"
- source = google_compute_disk.disks[disk.key].name
- }
- }
-
- boot_disk {
- initialize_params {
- type = "pd-ssd"
- image = (
- var.test_instance_defaults.image == null
- ? "projects/cos-cloud/global/images/family/cos-stable"
- : var.test_instance_defaults.image
- )
- size = 10
- }
- }
-
- network_interface {
- network = var.test_instance.network
- subnetwork = var.test_instance.subnetwork
- dynamic "access_config" {
- for_each = var.test_instance_defaults.nat ? [""] : []
- iterator = config
- content {
- nat_ip = null
- }
- }
- }
-
- service_account {
- email = google_service_account.default[0].email
- scopes = ["https://www.googleapis.com/auth/cloud-platform"]
- }
-
-}
diff --git a/modules/cloud-config-container/mysql/README.md b/modules/cloud-config-container/mysql/README.md
index 535a77afe..31045804d 100644
--- a/modules/cloud-config-container/mysql/README.md
+++ b/modules/cloud-config-container/mysql/README.md
@@ -26,18 +26,31 @@ This example will create a `cloud-config` that uses the container's default conf
```hcl
module "cos-mysql" {
- source = "./fabric/modules/cos-container/mysql"
+ source = "./fabric/modules/cloud-config-container/mysql"
mysql_password = "foo"
}
-# use it as metadata in a compute instance or template
-module "vm-mysql" {
- source = "./fabric/modules/compute-vm"
+module "vm" {
+ source = "./fabric/modules/compute-vm"
+ project_id = "my-project"
+ zone = "europe-west8-b"
+ name = "cos-mysql"
+ network_interfaces = [{
+ network = "default"
+ subnetwork = "gce"
+ }]
metadata = {
user-data = module.cos-mysql.cloud_config
google-logging-enabled = true
}
+ boot_disk = {
+ image = "projects/cos-cloud/global/images/family/cos-stable"
+ type = "pd-ssd"
+ size = 10
+ }
+ tags = ["mysql", "ssh"]
}
+# tftest modules=1 resources=1
```
### Custom MySQL configuration and KMS encrypted password
@@ -46,35 +59,17 @@ This example will create a `cloud-config` that uses a custom MySQL configuration
```hcl
module "cos-mysql" {
- source = "./fabric/modules/cos-container/mysql"
+ source = "./fabric/modules/cloud-config-container/mysql"
mysql_config = "./my.cnf"
mysql_password = "CiQAsd7WY=="
- kms_config = {
+ kms_config = {
project_id = "my-project"
keyring = "test-cos"
location = "europe-west1"
key = "mysql"
}
}
-```
-
-### MySQL instance
-
-This example shows how to create the single instance optionally managed by the module, providing all required attributes in the `test_instance` variable. The instance is purposefully kept simple and should only be used in development, or when designing infrastructures.
-
-```hcl
-module "cos-mysql" {
- source = "./fabric/modules/cos-container/mysql"
- mysql_password = "foo"
- test_instance = {
- project_id = "my-project"
- zone = "europe-west1-b"
- name = "cos-mysql"
- type = "n1-standard-1"
- network = "default"
- subnetwork = "https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west1/subnetworks/my-subnet"
- }
-}
+# tftest modules=0 resources=0
```
@@ -89,14 +84,11 @@ 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/mysql/instance.tf b/modules/cloud-config-container/mysql/instance.tf
deleted file mode 120000
index bdef596b6..000000000
--- a/modules/cloud-config-container/mysql/instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/mysql/outputs-instance.tf b/modules/cloud-config-container/mysql/outputs-instance.tf
deleted file mode 120000
index ea9e24045..000000000
--- a/modules/cloud-config-container/mysql/outputs-instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../outputs-instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/mysql/variables-instance.tf b/modules/cloud-config-container/mysql/variables-instance.tf
deleted file mode 120000
index 94af61e4d..000000000
--- a/modules/cloud-config-container/mysql/variables-instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../variables-instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/mysql/versions.tf b/modules/cloud-config-container/mysql/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/cloud-config-container/mysql/versions.tf
+++ b/modules/cloud-config-container/mysql/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/nginx-tls/README.md b/modules/cloud-config-container/nginx-tls/README.md
index d5790cf23..9dfe9b061 100644
--- a/modules/cloud-config-container/nginx-tls/README.md
+++ b/modules/cloud-config-container/nginx-tls/README.md
@@ -1,48 +1,37 @@
# Containerized Nginx with self-signed TLS on Container Optimized OS
-This module manages a `cloud-config` configuration that starts a containerized Nginx with a self-signed TLS cert on Container Optimized OS.
-This can be useful if you need quickly a VM or instance group answering HTTPS for prototyping.
+This module manages a `cloud-config` configuration that starts a containerized Nginx with a self-signed TLS cert on Container Optimized OS. This can be useful if you need quickly a VM or instance group answering HTTPS for prototyping.
The generated cloud config is rendered in the `cloud_config` output, and is meant to be used in instances or instance templates via the `user-data` metadata.
-This module depends on the [`cos-generic-metadata` module](../cos-generic-metadata) being in the parent folder. If you change its location be sure to adjust the `source` attribute in `main.tf`.
-
-## Examples
-
-### Default configuration
+## Example
```hcl
-# Nginx with self-signed TLS config
module "cos-nginx-tls" {
source = "./fabric/modules/cloud-config-container/nginx-tls"
}
-# COS VM
module "vm-nginx-tls" {
source = "./fabric/modules/compute-vm"
- project_id = local.project_id
- zone = local.zone
+ project_id = "my-project"
+ zone = "europe-west8-b"
name = "cos-nginx-tls"
network_interfaces = [{
- network = local.vpc.self_link,
- subnetwork = local.vpc.subnet_self_link,
- nat = false,
- addresses = null
+ network = "default"
+ subnetwork = "gce"
}]
-
metadata = {
user-data = module.cos-nginx-tls.cloud_config
google-logging-enabled = true
}
-
boot_disk = {
image = "projects/cos-cloud/global/images/family/cos-stable"
type = "pd-ssd"
size = 10
}
-
- service_account_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
+ tags = ["http-server", "https-server", "ssh"]
}
+# tftest modules=1 resources=1
```
@@ -50,11 +39,9 @@ module "vm-nginx-tls" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [files](variables.tf#L17) | Map of extra files to create on the instance, path as key. Owner and permissions will use defaults if null. | map(object({…})) | | null |
-| [nginx_image](variables.tf#L27) | Nginx container image to use. | string | | "nginx:1.23.1" |
-| [runcmd_post](variables.tf#L33) | Extra commands to run after starting nginx. | list(string) | | [] |
-| [runcmd_pre](variables.tf#L39) | Extra commands to run before starting nginx. | list(string) | | [] |
-| [users](variables.tf#L45) | Additional list of usernames to be created. | list(object({…})) | | […] |
+| [files](variables.tf#L17) | Map of extra files to create on the instance, path as key. Owner and permissions will use defaults if null. | map(object({…})) | | {} |
+| [hello](variables.tf#L28) | Behave like the nginx hello image by returning plain text informative responses. | bool | | true |
+| [image](variables.tf#L35) | Nginx container image to use. | string | | "nginx:1.23.1" |
## Outputs
diff --git a/modules/cloud-config-container/nginx-tls/assets/cloud-config.yaml b/modules/cloud-config-container/nginx-tls/assets/cloud-config.yaml
new file mode 100644
index 000000000..2b7ebe847
--- /dev/null
+++ b/modules/cloud-config-container/nginx-tls/assets/cloud-config.yaml
@@ -0,0 +1,63 @@
+#cloud-config
+
+# 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
+#
+# https://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.
+
+users:
+ - name: nginx
+ uid: 2000
+
+write_files:
+ - path: /var/lib/docker/daemon.json
+ permissions: "0644"
+ owner: root
+ content: |
+ {
+ "live-restore": true,
+ "storage-driver": "overlay2",
+ "log-opts": {
+ "max-size": "1024m"
+ }
+ }
+ # nginx container service
+ - path: /etc/systemd/system/nginx.service
+ permissions: "0644"
+ owner: root
+ content: |
+ [Unit]
+ Description=Start nginx container
+ After=gcr-online.target docker.socket
+ Wants=gcr-online.target docker.socket docker-events-collector.service
+ [Service]
+ Environment="HOME=/home/nginx"
+ ExecStart=/usr/bin/docker run --rm --name=nginx \
+ --network host --pid host \
+ -v /etc/nginx/conf.d:/etc/nginx/conf.d \
+ -v /etc/ssl:/etc/ssl \
+ ${image}
+ ExecStop=/usr/bin/docker stop nginx
+%{ for k, v in files ~}
+ - path: ${k}
+ owner: ${v.owner}
+ permissions: "${v.permissions}"
+ content: |
+ ${indent(6, v.content)}
+%{ endfor ~}
+
+runcmd:
+ - iptables -I INPUT 1 -p tcp -m tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
+ - iptables -I INPUT 1 -p tcp -m tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
+ - /var/run/nginx/customize.sh
+ - systemctl daemon-reload
+ - systemctl start nginx
diff --git a/modules/cloud-config-container/nginx-tls/files/customize.sh b/modules/cloud-config-container/nginx-tls/assets/customize.sh
similarity index 72%
rename from modules/cloud-config-container/nginx-tls/files/customize.sh
rename to modules/cloud-config-container/nginx-tls/assets/customize.sh
index afbf56db9..22b40064b 100644
--- a/modules/cloud-config-container/nginx-tls/files/customize.sh
+++ b/modules/cloud-config-container/nginx-tls/assets/customize.sh
@@ -13,8 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-FQDN=$(curl -s -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/hostname)
+FQDN=$(\
+ curl -s -H "Metadata-Flavor: Google" \
+ http://metadata/computeMetadata/v1/instance/hostname)
HOSTNAME=$(echo $FQDN | cut -d"." -f1)
-openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj /CN=$HOSTNAME/ -addext "subjectAltName = DNS:$FQDN" -keyout /etc/ssl/self-signed.key -out /etc/ssl/self-signed.crt
+openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \
+ -subj /CN=$HOSTNAME/ -addext "subjectAltName = DNS:$FQDN" \
+ -keyout /etc/ssl/self-signed.key -out /etc/ssl/self-signed.crt
chgrp nginx /etc/ssl/self-signed.key -out /etc/ssl/self-signed.crt
sed -i "s/HOSTNAME/${HOSTNAME}/" /etc/nginx/conf.d/default.conf
\ No newline at end of file
diff --git a/modules/cloud-config-container/nginx-tls/assets/default.conf b/modules/cloud-config-container/nginx-tls/assets/default.conf
new file mode 100644
index 000000000..2be98ff27
--- /dev/null
+++ b/modules/cloud-config-container/nginx-tls/assets/default.conf
@@ -0,0 +1,24 @@
+server {
+ listen 80;
+ listen 443 ssl;
+ server_name HOSTNAME;
+ ssl_certificate /etc/ssl/self-signed.crt;
+ ssl_certificate_key /etc/ssl/self-signed.key;
+
+ location / {
+ {% if hello %}
+ default_type text/plain;
+ expires -1;
+ return 200 'Server address: $server_addr:$server_port\nServer name: $hostname\nDate: $time_local\nURI: $request_uri\nRequest ID: $request_id\n';
+ {% else %}
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ {% endif %}
+ }
+
+ error_page 500 502 503 504 /50x.html;
+
+ location = /50x.html {
+ root /usr/share/nginx/html;
+ }
+}
\ No newline at end of file
diff --git a/modules/cloud-config-container/nginx-tls/files/default.conf b/modules/cloud-config-container/nginx-tls/files/default.conf
deleted file mode 100644
index b928902a0..000000000
--- a/modules/cloud-config-container/nginx-tls/files/default.conf
+++ /dev/null
@@ -1,20 +0,0 @@
-server {
- listen 80;
- listen 443 ssl;
- server_name HOSTNAME;
- ssl_certificate /etc/ssl/self-signed.crt;
- ssl_certificate_key /etc/ssl/self-signed.key;
-
-
- location / {
- root /usr/share/nginx/html;
- index index.html index.htm;
- }
-
- error_page 500 502 503 504 /50x.html;
-
- location = /50x.html {
- root /usr/share/nginx/html;
- }
-
-}
\ No newline at end of file
diff --git a/modules/cloud-config-container/nginx-tls/main.tf b/modules/cloud-config-container/nginx-tls/main.tf
deleted file mode 100644
index 809e9a8aa..000000000
--- a/modules/cloud-config-container/nginx-tls/main.tf
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * 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 {
- default_files = {
- "/var/run/nginx/customize.sh" = {
- content = file("${path.module}/files/customize.sh")
- owner = "root"
- permissions = "0744"
- }
- "/etc/nginx/conf.d/default.conf" = {
- content = file("${path.module}/files/default.conf")
- owner = "root"
- permissions = "0644"
- }
- }
- files = var.files != null ? merge(local.default_files, var.files) : local.default_files
-}
-
-module "cos-envoy-td" {
- source = "../cos-generic-metadata"
-
- authenticate_gcr = true
- users = concat([
- {
- username = "nginx"
- uid = 2000
- }
- ], var.users)
- run_as_first_user = false
-
- boot_commands = [
- "systemctl start node-problem-detector",
- ]
-
- container_image = var.nginx_image
- container_name = "nginx"
- container_args = ""
-
- container_volumes = [
- { host = "/etc/nginx/conf.d", container = "/etc/nginx/conf.d" },
- { host = "/etc/ssl", container = "/etc/ssl" },
- ]
-
- docker_args = "--network host --pid host"
-
- files = local.files
-
- run_commands = concat(var.runcmd_pre, [
- "iptables -I INPUT 1 -p tcp -m tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT",
- "iptables -I INPUT 1 -p tcp -m tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT",
- "/var/run/nginx/customize.sh",
- "systemctl daemon-reload",
- "systemctl start nginx",
- ], var.runcmd_post)
-
-}
diff --git a/modules/cloud-config-container/nginx-tls/outputs.tf b/modules/cloud-config-container/nginx-tls/outputs.tf
index 4ce8d2473..2acd83f64 100644
--- a/modules/cloud-config-container/nginx-tls/outputs.tf
+++ b/modules/cloud-config-container/nginx-tls/outputs.tf
@@ -16,5 +16,23 @@
output "cloud_config" {
description = "Rendered cloud-config file to be passed as user-data instance metadata."
- value = module.cos-envoy-td.cloud_config
+ value = templatefile("${path.module}/assets/cloud-config.yaml", {
+ files = merge(
+ {
+ "/var/run/nginx/customize.sh" = {
+ content = file("${path.module}/assets/customize.sh")
+ owner = "root"
+ permissions = "0744"
+ }
+ "/etc/nginx/conf.d/default.conf" = {
+ content = templatefile(
+ "${path.module}/assets/default.conf", { hello = var.hello }
+ )
+ owner = "root"
+ permissions = "0644"
+ }
+ }, var.files
+ )
+ image = var.image
+ })
}
diff --git a/modules/cloud-config-container/nginx-tls/variables.tf b/modules/cloud-config-container/nginx-tls/variables.tf
index 9ca826266..f3cab5826 100644
--- a/modules/cloud-config-container/nginx-tls/variables.tf
+++ b/modules/cloud-config-container/nginx-tls/variables.tf
@@ -18,38 +18,22 @@ variable "files" {
description = "Map of extra files to create on the instance, path as key. Owner and permissions will use defaults if null."
type = map(object({
content = string
- owner = string
- permissions = string
+ owner = optional(string, "root")
+ permissions = optional(string, "0644")
}))
- default = null
+ default = {}
+ nullable = false
}
-variable "nginx_image" {
+variable "hello" {
+ description = "Behave like the nginx hello image by returning plain text informative responses."
+ type = bool
+ default = true
+ nullable = false
+}
+
+variable "image" {
description = "Nginx container image to use."
type = string
default = "nginx:1.23.1"
}
-
-variable "runcmd_post" {
- description = "Extra commands to run after starting nginx."
- type = list(string)
- default = []
-}
-
-variable "runcmd_pre" {
- description = "Extra commands to run before starting nginx."
- type = list(string)
- default = []
-}
-
-variable "users" {
- description = "Additional list of usernames to be created."
- type = list(object({
- username = string,
- uid = number,
- }))
- default = [
- ]
-}
-
-
diff --git a/modules/cloud-config-container/nginx-tls/versions.tf b/modules/cloud-config-container/nginx-tls/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/cloud-config-container/nginx-tls/versions.tf
+++ b/modules/cloud-config-container/nginx-tls/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/nginx/README.md b/modules/cloud-config-container/nginx/README.md
index 12ca3d5d0..c5fbc09bd 100644
--- a/modules/cloud-config-container/nginx/README.md
+++ b/modules/cloud-config-container/nginx/README.md
@@ -24,35 +24,30 @@ This example will create a `cloud-config` that uses the module's defaults, creat
```hcl
module "cos-nginx" {
- source = "./fabric/modules/cloud-config-container/nginx"
+ source = "./fabric/modules/cloud-config-container/nginx"
}
-# use it as metadata in a compute instance or template
-module "vm-nginx" {
- source = "./fabric/modules/compute-vm"
+module "vm-nginx-tls" {
+ source = "./fabric/modules/compute-vm"
+ project_id = "my-project"
+ zone = "europe-west8-b"
+ name = "cos-nginx"
+ network_interfaces = [{
+ network = "default"
+ subnetwork = "gce"
+ }]
metadata = {
user-data = module.cos-nginx.cloud_config
google-logging-enabled = true
}
-}
-```
-
-### Nginx instance
-
-This example shows how to create the single instance optionally managed by the module, providing all required attributes in the `test_instance` variable. The instance is purposefully kept simple and should only be used in development, or when designing infrastructures.
-
-```hcl
-module "cos-nginx" {
- source = "./fabric/modules/cloud-config-container/nginx"
- test_instance = {
- project_id = "my-project"
- zone = "europe-west1-b"
- name = "cos-nginx"
- type = "f1-micro"
- network = "default"
- subnetwork = "https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west1/subnetworks/my-subnet"
+ boot_disk = {
+ image = "projects/cos-cloud/global/images/family/cos-stable"
+ type = "pd-ssd"
+ size = 10
}
+ tags = ["http-server", "ssh"]
}
+# tftest modules=1 resources=1
```
@@ -68,8 +63,6 @@ module "cos-nginx" {
| [nginx_config](variables.tf#L57) | Nginx configuration path, if null container default will be used. | string | | null |
| [runcmd_post](variables.tf#L63) | Extra commands to run after starting nginx. | list(string) | | [] |
| [runcmd_pre](variables.tf#L69) | Extra commands to run before starting nginx. | list(string) | | [] |
-| [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({…}) | | {…} |
| [users](variables.tf#L75) | List of additional usernames to be created. | list(object({…})) | | […] |
## Outputs
@@ -77,6 +70,5 @@ module "cos-nginx" {
| 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/instance.tf b/modules/cloud-config-container/nginx/instance.tf
deleted file mode 120000
index bdef596b6..000000000
--- a/modules/cloud-config-container/nginx/instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/nginx/outputs-instance.tf b/modules/cloud-config-container/nginx/outputs-instance.tf
deleted file mode 120000
index ea9e24045..000000000
--- a/modules/cloud-config-container/nginx/outputs-instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../outputs-instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/nginx/variables-instance.tf b/modules/cloud-config-container/nginx/variables-instance.tf
deleted file mode 120000
index 94af61e4d..000000000
--- a/modules/cloud-config-container/nginx/variables-instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../variables-instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/nginx/versions.tf b/modules/cloud-config-container/nginx/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/cloud-config-container/nginx/versions.tf
+++ b/modules/cloud-config-container/nginx/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/onprem/instance.tf b/modules/cloud-config-container/onprem/instance.tf
deleted file mode 120000
index bdef596b6..000000000
--- a/modules/cloud-config-container/onprem/instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/onprem/outputs-instance.tf b/modules/cloud-config-container/onprem/outputs-instance.tf
deleted file mode 120000
index ea9e24045..000000000
--- a/modules/cloud-config-container/onprem/outputs-instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../outputs-instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/onprem/variables-instance.tf b/modules/cloud-config-container/onprem/variables-instance.tf
deleted file mode 120000
index 94af61e4d..000000000
--- a/modules/cloud-config-container/onprem/variables-instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../variables-instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/outputs-instance.tf b/modules/cloud-config-container/outputs-instance.tf
deleted file mode 100644
index 8c657eb05..000000000
--- a/modules/cloud-config-container/outputs-instance.tf
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * 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.
- */
-
-output "test_instance" {
- description = "Optional test instance name and address."
- value = (var.test_instance == null ? {} : {
- address = google_compute_instance.default[0].network_interface.0.network_ip
- name = google_compute_instance.default[0].name
- nat_address = try(
- google_compute_instance.default[0].network_interface.0.access_config.0.nat_ip,
- null
- )
- service_account = google_service_account.default[0].email
- })
-}
diff --git a/modules/cloud-config-container/simple-nva/README.md b/modules/cloud-config-container/simple-nva/README.md
index 0e495df5b..f70842e8c 100644
--- a/modules/cloud-config-container/simple-nva/README.md
+++ b/modules/cloud-config-container/simple-nva/README.md
@@ -9,7 +9,6 @@ This NVA can be used to interconnect up to 8 VPCs.
### Simple example
```hcl
-# Interfaces configuration
locals {
network_interfaces = [
{
@@ -28,41 +27,40 @@ locals {
routes = ["10.0.0.0/9"]
subnetwork = "prod_vpc_nva_subnet_self_link"
}
+ ]
}
-# NVA config
-module "nva-cloud-config" {
- source = "../../../cloud-foundation-fabric/modules/cloud-config-container/simple-nva"
+module "cos-nva" {
+ source = "./fabric/modules/cloud-config-container/simple-nva"
enable_health_checks = true
network_interfaces = local.network_interfaces
- files = {
- "/var/lib/cloud/scripts/per-boot/firewall-rules.sh" = {
- content = file("./your_path/to/firewall-rules.sh")
- owner = "root"
- permissions = 0700
- }
- }
+ # files = {
+ # "/var/lib/cloud/scripts/per-boot/firewall-rules.sh" = {
+ # content = file("./your_path/to/firewall-rules.sh")
+ # owner = "root"
+ # permissions = 0700
+ # }
+ # }
}
-# COS VM
-module "nva" {
- source = "../../modules/compute-vm"
- project_id = "myproject"
- instance_type = "e2-standard-2"
- name = "nva"
- can_ip_forward = true
- zone = "europe-west8-a"
- tags = ["nva"]
+module "vm" {
+ source = "./fabric/modules/compute-vm"
+ project_id = "my-project"
+ zone = "europe-west8-b"
+ name = "cos-nva"
network_interfaces = local.network_interfaces
+ metadata = {
+ user-data = module.cos-nva.cloud_config
+ google-logging-enabled = true
+ }
boot_disk = {
image = "projects/cos-cloud/global/images/family/cos-stable"
+ type = "pd-ssd"
size = 10
- type = "pd-balanced"
- }
- metadata = {
- user-data = module.nva-cloud-config.cloud_config
}
+ tags = ["nva", "ssh"]
}
+# tftest modules=1 resources=1
```
@@ -74,14 +72,11 @@ module "nva" {
| [cloud_config](variables.tf#L17) | Cloud config template path. If null default will be used. | string | | null |
| [enable_health_checks](variables.tf#L23) | Configures routing to enable responses to health check probes. | bool | | false |
| [files](variables.tf#L29) | 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/simple-nva/files/policy_based_routing.sh b/modules/cloud-config-container/simple-nva/files/policy_based_routing.sh
index 2e1eb1523..951396d35 100644
--- a/modules/cloud-config-container/simple-nva/files/policy_based_routing.sh
+++ b/modules/cloud-config-container/simple-nva/files/policy_based_routing.sh
@@ -17,16 +17,18 @@
IF_NAME=$1
IP_LB=$(ip r show table local | grep "$IF_NAME proto 66" | cut -f 2 -d " ")
-# If there's a load balancer for this IF...
-if [ ! -z $IP_LB ]
-then
- IF_NUMBER=$(echo $IF_NAME | sed -e s/eth//)
- IF_GW=$(curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/$IF_NUMBER/gateway -H "Metadata-Flavor: Google")
- IF_IP=$(curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/$IF_NUMBER/ip -H "Metadata-Flavor: Google")
- IF_NETMASK=$(curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/$IF_NUMBER/subnetmask -H "Metadata-Flavor: Google")
- IF_IP_PREFIX=$(/var/run/nva/ipprefix_by_netmask.sh $IF_NETMASK)
- grep -qxF "$((200 + $IF_NUMBER)) hc-$IF_NAME" /etc/iproute2/rt_tables || echo "$((200 + $IF_NUMBER)) hc-$IF_NAME" >>/etc/iproute2/rt_tables
- ip route add $IF_GW src $IF_IP dev $IF_NAME table hc-$IF_NAME
- ip route add default via $IF_GW dev $IF_NAME table hc-$IF_NAME
- ip rule add from $IP_LB/32 table hc-$IF_NAME
-fi
+# Sleep while there's no load balancer IP route for this IF
+while [ -z $IP_LB ] ; do
+ sleep 2
+ IP_LB=$(ip r show table local | grep "$IF_NAME proto 66" | cut -f 2 -d " ")
+done
+
+IF_NUMBER=$(echo $IF_NAME | sed -e s/eth//)
+IF_GW=$(curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/$IF_NUMBER/gateway -H "Metadata-Flavor: Google")
+IF_IP=$(curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/$IF_NUMBER/ip -H "Metadata-Flavor: Google")
+IF_NETMASK=$(curl http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/$IF_NUMBER/subnetmask -H "Metadata-Flavor: Google")
+IF_IP_PREFIX=$(/var/run/nva/ipprefix_by_netmask.sh $IF_NETMASK)
+grep -qxF "$((200 + $IF_NUMBER)) hc-$IF_NAME" /etc/iproute2/rt_tables || echo "$((200 + $IF_NUMBER)) hc-$IF_NAME" >>/etc/iproute2/rt_tables
+ip route add $IF_GW src $IF_IP dev $IF_NAME table hc-$IF_NAME
+ip route add default via $IF_GW dev $IF_NAME table hc-$IF_NAME
+ip rule add from $IP_LB/32 table hc-$IF_NAME
diff --git a/modules/cloud-config-container/simple-nva/instance.tf b/modules/cloud-config-container/simple-nva/instance.tf
deleted file mode 120000
index bdef596b6..000000000
--- a/modules/cloud-config-container/simple-nva/instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/simple-nva/outputs-instance.tf b/modules/cloud-config-container/simple-nva/outputs-instance.tf
deleted file mode 120000
index ea9e24045..000000000
--- a/modules/cloud-config-container/simple-nva/outputs-instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../outputs-instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/simple-nva/variables-instance.tf b/modules/cloud-config-container/simple-nva/variables-instance.tf
deleted file mode 120000
index 94af61e4d..000000000
--- a/modules/cloud-config-container/simple-nva/variables-instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../variables-instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/simple-nva/versions.tf b/modules/cloud-config-container/simple-nva/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/cloud-config-container/simple-nva/versions.tf
+++ b/modules/cloud-config-container/simple-nva/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/squid/README.md b/modules/cloud-config-container/squid/README.md
index 1c866b25a..4ee29ed91 100644
--- a/modules/cloud-config-container/squid/README.md
+++ b/modules/cloud-config-container/squid/README.md
@@ -24,39 +24,32 @@ This example will create a `cloud-config` that allows any client in the 10.0.0.0
```hcl
module "cos-squid" {
- source = "./fabric/modules/cloud-config-container/squid"
- whitelist = [".github.com"]
- clients = ["10.0.0.0/8"]
+ source = "./fabric/modules/cloud-config-container/squid"
+ allow = [".github.com"]
+ clients = ["10.0.0.0/8"]
}
-# use it as metadata in a compute instance or template
-module "vm-squid" {
- source = "./fabric/modules/compute-vm"
+module "vm" {
+ source = "./fabric/modules/compute-vm"
+ project_id = "my-project"
+ zone = "europe-west8-b"
+ name = "cos-squid"
+ network_interfaces = [{
+ network = "default"
+ subnetwork = "gce"
+ }]
metadata = {
user-data = module.cos-squid.cloud_config
google-logging-enabled = true
}
-}
-```
-
-### Test Squid instance
-
-This example shows how to create the single instance optionally managed by the module, providing all required attributes in the `test_instance` variable. The instance is purposefully kept simple and should only be used in development, or when designing infrastructures.
-
-```hcl
-module "cos-squid" {
- source = "./fabric/modules/cloud-config-container/squid"
- whitelist = ["github.com"]
- clients = ["10.0.0.0/8"]
- test_instance = {
- project_id = "my-project"
- zone = "europe-west1-b"
- name = "cos-squid"
- type = "f1-micro"
- network = "default"
- subnetwork = "https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west1/subnetworks/my-subnet"
+ boot_disk = {
+ image = "projects/cos-cloud/global/images/family/cos-stable"
+ type = "pd-ssd"
+ size = 10
}
+ tags = ["http-server", "ssh"]
}
+# tftest modules=1 resources=1
```
@@ -73,14 +66,11 @@ module "cos-squid" {
| [file_defaults](variables.tf#L58) | Default owner and permissions for files. | object({…}) | | {…} |
| [files](variables.tf#L70) | 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#L80) | 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/cloud-config-container/squid/instance.tf b/modules/cloud-config-container/squid/instance.tf
deleted file mode 120000
index bdef596b6..000000000
--- a/modules/cloud-config-container/squid/instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/squid/outputs-instance.tf b/modules/cloud-config-container/squid/outputs-instance.tf
deleted file mode 120000
index ea9e24045..000000000
--- a/modules/cloud-config-container/squid/outputs-instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../outputs-instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/squid/variables-instance.tf b/modules/cloud-config-container/squid/variables-instance.tf
deleted file mode 120000
index 94af61e4d..000000000
--- a/modules/cloud-config-container/squid/variables-instance.tf
+++ /dev/null
@@ -1 +0,0 @@
-../variables-instance.tf
\ No newline at end of file
diff --git a/modules/cloud-config-container/squid/versions.tf b/modules/cloud-config-container/squid/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/cloud-config-container/squid/versions.tf
+++ b/modules/cloud-config-container/squid/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/variables-instance.tf b/modules/cloud-config-container/variables-instance.tf
deleted file mode 100644
index 3697133ea..000000000
--- a/modules/cloud-config-container/variables-instance.tf
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * 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 "test_instance" {
- description = "Test/development instance attributes, leave null to skip creation."
- type = object({
- project_id = string
- zone = string
- name = string
- type = string
- network = string
- subnetwork = string
- })
- default = null
-}
-
-variable "test_instance_defaults" {
- description = "Test/development instance defaults used for optional configuration. If image is null, COS stable will be used."
- type = object({
- disks = map(object({
- read_only = bool
- size = number
- }))
- image = string
- metadata = map(string)
- nat = bool
- service_account_roles = list(string)
- tags = list(string)
- })
- default = {
- disks = {}
- image = null
- metadata = {}
- nat = false
- service_account_roles = [
- "roles/logging.logWriter",
- "roles/monitoring.metricWriter"
- ]
- tags = ["ssh"]
- }
-}
diff --git a/modules/cloud-function/README.md b/modules/cloud-function/README.md
index 75ef37198..ebf300fcc 100644
--- a/modules/cloud-function/README.md
+++ b/modules/cloud-function/README.md
@@ -16,10 +16,10 @@ This deploys a Cloud Function with an HTTP endpoint, using a pre-existing GCS bu
```hcl
module "cf-http" {
- source = "./fabric/modules/cloud-function"
- project_id = "my-project"
- name = "test-cf-http"
- bucket_name = "test-cf-bundles"
+ source = "./fabric/modules/cloud-function"
+ project_id = "my-project"
+ name = "test-cf-http"
+ bucket_name = "test-cf-bundles"
bundle_config = {
source_dir = "fabric/assets/"
output_path = "bundle.zip"
@@ -31,11 +31,11 @@ module "cf-http" {
Analogous example using 2nd generation Cloud Functions
```hcl
module "cf-http" {
- source = "./fabric/modules/cloud-function"
- v2 = true
- project_id = "my-project"
- name = "test-cf-http"
- bucket_name = "test-cf-bundles"
+ source = "./fabric/modules/cloud-function"
+ v2 = true
+ project_id = "my-project"
+ name = "test-cf-http"
+ bucket_name = "test-cf-bundles"
bundle_config = {
source_dir = "fabric/assets/"
output_path = "bundle.zip"
@@ -111,15 +111,15 @@ To allow anonymous access to the function, grant the `roles/cloudfunctions.invok
```hcl
module "cf-http" {
- source = "./fabric/modules/cloud-function"
- project_id = "my-project"
- name = "test-cf-http"
- bucket_name = "test-cf-bundles"
+ source = "./fabric/modules/cloud-function"
+ project_id = "my-project"
+ name = "test-cf-http"
+ bucket_name = "test-cf-bundles"
bundle_config = {
source_dir = "fabric/assets/"
output_path = "bundle.zip"
}
- iam = {
+ iam = {
"roles/cloudfunctions.invoker" = ["allUsers"]
}
}
@@ -132,15 +132,15 @@ You can have the module auto-create the GCS bucket used for deployment via the `
```hcl
module "cf-http" {
- source = "./fabric/modules/cloud-function"
- project_id = "my-project"
- name = "test-cf-http"
- bucket_name = "test-cf-bundles"
+ source = "./fabric/modules/cloud-function"
+ project_id = "my-project"
+ name = "test-cf-http"
+ bucket_name = "test-cf-bundles"
bucket_config = {
lifecycle_delete_age_days = 1
}
bundle_config = {
- source_dir = "fabric/assets/"
+ source_dir = "fabric/assets/"
}
}
# tftest modules=1 resources=3
@@ -152,10 +152,10 @@ To use a custom service account managed by the module, set `service_account_crea
```hcl
module "cf-http" {
- source = "./fabric/modules/cloud-function"
- project_id = "my-project"
- name = "test-cf-http"
- bucket_name = "test-cf-bundles"
+ source = "./fabric/modules/cloud-function"
+ project_id = "my-project"
+ name = "test-cf-http"
+ bucket_name = "test-cf-bundles"
bundle_config = {
source_dir = "fabric/assets/"
output_path = "bundle.zip"
@@ -169,10 +169,10 @@ To use an externally managed service account, pass its email in `service_account
```hcl
module "cf-http" {
- source = "./fabric/modules/cloud-function"
- project_id = "my-project"
- name = "test-cf-http"
- bucket_name = "test-cf-bundles"
+ source = "./fabric/modules/cloud-function"
+ project_id = "my-project"
+ name = "test-cf-http"
+ bucket_name = "test-cf-bundles"
bundle_config = {
source_dir = "fabric/assets/"
output_path = "bundle.zip"
@@ -188,10 +188,10 @@ In order to help prevent `archive_zip.output_md5` from changing cross platform (
```hcl
module "cf-http" {
- source = "./fabric/modules/cloud-function"
- project_id = "my-project"
- name = "test-cf-http"
- bucket_name = "test-cf-bundles"
+ source = "./fabric/modules/cloud-function"
+ project_id = "my-project"
+ name = "test-cf-http"
+ bucket_name = "test-cf-bundles"
bundle_config = {
source_dir = "fabric/assets"
output_path = "bundle.zip"
@@ -207,10 +207,10 @@ This deploys a Cloud Function with an HTTP endpoint, using a pre-existing GCS bu
```hcl
module "cf-http" {
- source = "./fabric/modules/cloud-function"
- project_id = "my-project"
- name = "test-cf-http"
- bucket_name = "test-cf-bundles"
+ source = "./fabric/modules/cloud-function"
+ project_id = "my-project"
+ name = "test-cf-http"
+ bucket_name = "test-cf-bundles"
build_worker_pool = "projects/my-project/locations/europe-west1/workerPools/my_build_worker_pool"
bundle_config = {
source_dir = "fabric/assets"
diff --git a/modules/cloud-function/versions.tf b/modules/cloud-function/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/cloud-function/versions.tf
+++ b/modules/cloud-function/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/cloud-identity-group/README.md b/modules/cloud-identity-group/README.md
index 91c625fa2..c94aa6d8b 100644
--- a/modules/cloud-identity-group/README.md
+++ b/modules/cloud-identity-group/README.md
@@ -46,7 +46,7 @@ module "group" {
]
managers = [
"user3@example.com"
- ]
+ ]
}
# tftest modules=1 resources=5
```
diff --git a/modules/cloud-identity-group/versions.tf b/modules/cloud-identity-group/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/cloud-identity-group/versions.tf
+++ b/modules/cloud-identity-group/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/cloud-run/README.md b/modules/cloud-run/README.md
index e8e2fc1ff..5fde6ed47 100644
--- a/modules/cloud-run/README.md
+++ b/modules/cloud-run/README.md
@@ -14,18 +14,18 @@ module "cloud_run" {
project_id = "my-project"
name = "hello"
containers = [{
- image = "us-docker.pkg.dev/cloudrun/container/hello"
+ image = "us-docker.pkg.dev/cloudrun/container/hello"
options = {
command = null
args = null
- env = {
- "VAR1": "VALUE1",
- "VAR2": "VALUE2",
+ env = {
+ "VAR1" : "VALUE1",
+ "VAR2" : "VALUE2",
}
env_from = null
}
- ports = null
- resources = null
+ ports = null
+ resources = null
volume_mounts = null
}]
}
@@ -42,18 +42,18 @@ module "cloud_run" {
containers = [{
image = "us-docker.pkg.dev/cloudrun/container/hello"
options = {
- command = null
- args = null
- env = null
- env_from = {
- "CREDENTIALS": {
+ command = null
+ args = null
+ env = null
+ env_from = {
+ "CREDENTIALS" : {
name = "credentials"
- key = "1"
+ key = "1"
}
}
}
- ports = null
- resources = null
+ ports = null
+ resources = null
volume_mounts = null
}]
}
@@ -64,26 +64,26 @@ module "cloud_run" {
```hcl
module "cloud_run" {
- source = "./fabric/modules/cloud-run"
- project_id = var.project_id
- name = "hello"
- region = var.region
+ source = "./fabric/modules/cloud-run"
+ project_id = var.project_id
+ name = "hello"
+ region = var.region
revision_name = "green"
containers = [{
- image = "us-docker.pkg.dev/cloudrun/container/hello"
- options = null
- ports = null
- resources = null
+ image = "us-docker.pkg.dev/cloudrun/container/hello"
+ options = null
+ ports = null
+ resources = null
volume_mounts = {
- "credentials": "/credentials"
+ "credentials" : "/credentials"
}
}]
volumes = [
{
- name = "credentials"
+ name = "credentials"
secret_name = "credentials"
items = [{
- key = "1"
+ key = "1"
path = "v1.txt"
}]
}
@@ -98,9 +98,9 @@ This deploys a Cloud Run service with traffic split between two revisions.
```hcl
module "cloud_run" {
- source = "./fabric/modules/cloud-run"
- project_id = "my-project"
- name = "hello"
+ source = "./fabric/modules/cloud-run"
+ project_id = "my-project"
+ name = "hello"
revision_name = "green"
containers = [{
image = "us-docker.pkg.dev/cloudrun/container/hello"
@@ -110,7 +110,7 @@ module "cloud_run" {
volume_mounts = null
}]
traffic = {
- "blue" = 25
+ "blue" = 25
"green" = 75
}
}
@@ -159,8 +159,8 @@ module "cloud_run" {
}]
audit_log_triggers = [
{
- service_name = "cloudresourcemanager.googleapis.com"
- method_name = "SetIamPolicy"
+ service_name = "cloudresourcemanager.googleapis.com"
+ method_name = "SetIamPolicy"
}
]
}
diff --git a/modules/cloud-run/versions.tf b/modules/cloud-run/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/cloud-run/versions.tf
+++ b/modules/cloud-run/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/cloudsql-instance/README.md b/modules/cloudsql-instance/README.md
index a902f5455..9e05b0b38 100644
--- a/modules/cloudsql-instance/README.md
+++ b/modules/cloudsql-instance/README.md
@@ -88,7 +88,7 @@ module "db" {
# generatea password for user1
user1 = null
# assign a password to user2
- user2 = "mypassword"
+ user2 = "mypassword"
}
}
# tftest modules=1 resources=6
@@ -146,27 +146,29 @@ module "db" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [database_version](variables.tf#L49) | Database type and version to create. | string | ✓ | |
-| [name](variables.tf#L102) | Name of primary instance. | string | ✓ | |
-| [network](variables.tf#L107) | VPC self link where the instances will be deployed. Private Service Networking must be enabled and configured in this VPC. | string | ✓ | |
-| [project_id](variables.tf#L122) | The ID of the project where this instances will be created. | string | ✓ | |
-| [region](variables.tf#L127) | Region of the primary instance. | string | ✓ | |
-| [tier](variables.tf#L147) | The machine type to use for the instances. | string | ✓ | |
-| [authorized_networks](variables.tf#L17) | Map of NAME=>CIDR_RANGE to allow to connect to the database(s). | map(string) | | null |
-| [availability_type](variables.tf#L23) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | string | | "ZONAL" |
-| [backup_configuration](variables.tf#L29) | Backup settings for primary instance. Will be automatically enabled if using MySQL with one or more replicas. | object({…}) | | {…} |
-| [databases](variables.tf#L54) | Databases to create once the primary instance is created. | list(string) | | null |
-| [deletion_protection](variables.tf#L60) | Allow terraform to delete instances. | bool | | false |
-| [disk_size](variables.tf#L66) | Disk size in GB. Set to null to enable autoresize. | number | | null |
-| [disk_type](variables.tf#L72) | The type of data disk: `PD_SSD` or `PD_HDD`. | string | | "PD_SSD" |
-| [encryption_key_name](variables.tf#L78) | The full path to the encryption key used for the CMEK disk encryption of the primary instance. | string | | null |
-| [flags](variables.tf#L84) | Map FLAG_NAME=>VALUE for database-specific tuning. | map(string) | | null |
-| [ipv4_enabled](variables.tf#L90) | Add a public IP address to database instance. | bool | | false |
-| [labels](variables.tf#L96) | Labels to be attached to all instances. | map(string) | | null |
-| [prefix](variables.tf#L112) | Optional prefix used to generate instance names. | string | | null |
-| [replicas](variables.tf#L132) | Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation. | map(object({…})) | | {} |
-| [root_password](variables.tf#L141) | Root password of the Cloud SQL instance. Required for MS SQL Server. | string | | null |
-| [users](variables.tf#L152) | Map of users to create in the primary instance (and replicated to other replicas) in the format USER=>PASSWORD. For MySQL, anything afterr the first `@` (if persent) will be used as the user's host. Set PASSWORD to null if you want to get an autogenerated password. | map(string) | | null |
+| [database_version](variables.tf#L61) | Database type and version to create. | string | ✓ | |
+| [name](variables.tf#L114) | Name of primary instance. | string | ✓ | |
+| [network](variables.tf#L119) | VPC self link where the instances will be deployed. Private Service Networking must be enabled and configured in this VPC. | string | ✓ | |
+| [project_id](variables.tf#L140) | The ID of the project where this instances will be created. | string | ✓ | |
+| [region](variables.tf#L145) | Region of the primary instance. | string | ✓ | |
+| [tier](variables.tf#L165) | The machine type to use for the instances. | string | ✓ | |
+| [allocated_ip_ranges](variables.tf#L17) | (Optional)The name of the allocated ip range for the private ip CloudSQL instance. For example: \"google-managed-services-default\". If set, the instance ip will be created in the allocated range. The range name must comply with RFC 1035. Specifically, the name must be 1-63 characters long and match the regular expression a-z?. | object({…}) | | {} |
+| [authorized_networks](variables.tf#L26) | Map of NAME=>CIDR_RANGE to allow to connect to the database(s). | map(string) | | null |
+| [availability_type](variables.tf#L32) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | string | | "ZONAL" |
+| [backup_configuration](variables.tf#L38) | Backup settings for primary instance. Will be automatically enabled if using MySQL with one or more replicas. | object({…}) | | {…} |
+| [databases](variables.tf#L66) | Databases to create once the primary instance is created. | list(string) | | null |
+| [deletion_protection](variables.tf#L72) | Allow terraform to delete instances. | bool | | false |
+| [disk_size](variables.tf#L78) | Disk size in GB. Set to null to enable autoresize. | number | | null |
+| [disk_type](variables.tf#L84) | The type of data disk: `PD_SSD` or `PD_HDD`. | string | | "PD_SSD" |
+| [encryption_key_name](variables.tf#L90) | The full path to the encryption key used for the CMEK disk encryption of the primary instance. | string | | null |
+| [flags](variables.tf#L96) | Map FLAG_NAME=>VALUE for database-specific tuning. | map(string) | | null |
+| [ipv4_enabled](variables.tf#L102) | Add a public IP address to database instance. | bool | | false |
+| [labels](variables.tf#L108) | Labels to be attached to all instances. | map(string) | | null |
+| [postgres_client_certificates](variables.tf#L124) | Map of cert keys connect to the application(s) using public IP. | list(string) | | null |
+| [prefix](variables.tf#L130) | Optional prefix used to generate instance names. | string | | null |
+| [replicas](variables.tf#L150) | Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation. | map(object({…})) | | {} |
+| [root_password](variables.tf#L159) | Root password of the Cloud SQL instance. Required for MS SQL Server. | string | | null |
+| [users](variables.tf#L170) | Map of users to create in the primary instance (and replicated to other replicas) in the format USER=>PASSWORD. For MySQL, anything afterr the first `@` (if persent) will be used as the user's host. Set PASSWORD to null if you want to get an autogenerated password. | map(string) | | null |
## Outputs
@@ -181,8 +183,9 @@ module "db" {
| [ips](outputs.tf#L61) | IP addresses of all instances. | |
| [name](outputs.tf#L69) | Name of the primary instance. | |
| [names](outputs.tf#L74) | Names of all instances. | |
-| [self_link](outputs.tf#L82) | Self link of the primary instance. | |
-| [self_links](outputs.tf#L87) | Self links of all instances. | |
-| [user_passwords](outputs.tf#L95) | Map of containing the password of all users created through terraform. | ✓ |
+| [postgres_client_certificates](outputs.tf#L82) | The CA Certificate used to connect to the SQL Instance via SSL. | ✓ |
+| [self_link](outputs.tf#L88) | Self link of the primary instance. | |
+| [self_links](outputs.tf#L93) | Self links of all instances. | |
+| [user_passwords](outputs.tf#L101) | Map of containing the password of all users created through terraform. | ✓ |
diff --git a/modules/cloudsql-instance/main.tf b/modules/cloudsql-instance/main.tf
index f15b386bb..2419dc0d6 100644
--- a/modules/cloudsql-instance/main.tf
+++ b/modules/cloudsql-instance/main.tf
@@ -61,8 +61,9 @@ resource "google_sql_database_instance" "primary" {
user_labels = var.labels
ip_configuration {
- ipv4_enabled = var.ipv4_enabled
- private_network = var.network
+ ipv4_enabled = var.ipv4_enabled
+ private_network = var.network
+ allocated_ip_range = var.allocated_ip_ranges.primary
dynamic "authorized_networks" {
for_each = var.authorized_networks != null ? var.authorized_networks : {}
iterator = network
@@ -87,6 +88,7 @@ resource "google_sql_database_instance" "primary" {
)
start_time = var.backup_configuration.start_time
location = var.backup_configuration.location
+ point_in_time_recovery_enabled = var.backup_configuration.point_in_time_recovery_enabled
transaction_log_retention_days = var.backup_configuration.log_retention_days
backup_retention_settings {
retained_backups = var.backup_configuration.retention_count
@@ -126,8 +128,9 @@ resource "google_sql_database_instance" "replicas" {
user_labels = var.labels
ip_configuration {
- ipv4_enabled = var.ipv4_enabled
- private_network = var.network
+ ipv4_enabled = var.ipv4_enabled
+ private_network = var.network
+ allocated_ip_range = var.allocated_ip_ranges.replica
dynamic "authorized_networks" {
for_each = var.authorized_networks != null ? var.authorized_networks : {}
iterator = network
@@ -175,3 +178,10 @@ resource "google_sql_user" "users" {
host = each.value.host
password = each.value.password
}
+
+resource "google_sql_ssl_cert" "postgres_client_certificates" {
+ for_each = var.postgres_client_certificates != null ? toset(var.postgres_client_certificates) : toset([])
+ provider = google-beta
+ instance = google_sql_database_instance.primary.name
+ common_name = each.key
+}
diff --git a/modules/cloudsql-instance/outputs.tf b/modules/cloudsql-instance/outputs.tf
index 38bfc951b..8c814c06a 100644
--- a/modules/cloudsql-instance/outputs.tf
+++ b/modules/cloudsql-instance/outputs.tf
@@ -79,6 +79,12 @@ output "names" {
}
}
+output "postgres_client_certificates" {
+ description = "The CA Certificate used to connect to the SQL Instance via SSL."
+ value = google_sql_ssl_cert.postgres_client_certificates
+ sensitive = true
+}
+
output "self_link" {
description = "Self link of the primary instance."
value = google_sql_database_instance.primary.self_link
diff --git a/modules/cloudsql-instance/variables.tf b/modules/cloudsql-instance/variables.tf
index 04bff546b..c7c4ef8d0 100644
--- a/modules/cloudsql-instance/variables.tf
+++ b/modules/cloudsql-instance/variables.tf
@@ -14,6 +14,15 @@
* limitations under the License.
*/
+variable "allocated_ip_ranges" {
+ description = "(Optional)The name of the allocated ip range for the private ip CloudSQL instance. For example: \"google-managed-services-default\". If set, the instance ip will be created in the allocated range. The range name must comply with RFC 1035. Specifically, the name must be 1-63 characters long and match the regular expression a-z?."
+ type = object({
+ primary = optional(string)
+ replica = optional(string)
+ })
+ default = {}
+ nullable = false
+}
variable "authorized_networks" {
description = "Map of NAME=>CIDR_RANGE to allow to connect to the database(s)."
type = map(string)
@@ -28,21 +37,24 @@ variable "availability_type" {
variable "backup_configuration" {
description = "Backup settings for primary instance. Will be automatically enabled if using MySQL with one or more replicas."
+ nullable = false
type = object({
- enabled = bool
- binary_log_enabled = bool
- start_time = string
- location = string
- log_retention_days = number
- retention_count = number
+ enabled = optional(bool, false)
+ binary_log_enabled = optional(bool, false)
+ start_time = optional(string, "23:00")
+ location = optional(string)
+ log_retention_days = optional(number, 7)
+ point_in_time_recovery_enabled = optional(bool)
+ retention_count = optional(number, 7)
})
default = {
- enabled = false
- binary_log_enabled = false
- start_time = "23:00"
- location = null
- log_retention_days = 7
- retention_count = 7
+ enabled = false
+ binary_log_enabled = false
+ start_time = "23:00"
+ location = null
+ log_retention_days = 7
+ point_in_time_recovery_enabled = null
+ retention_count = 7
}
}
@@ -109,6 +121,12 @@ variable "network" {
type = string
}
+variable "postgres_client_certificates" {
+ description = "Map of cert keys connect to the application(s) using public IP."
+ type = list(string)
+ default = null
+}
+
variable "prefix" {
description = "Optional prefix used to generate instance names."
type = string
diff --git a/modules/cloudsql-instance/versions.tf b/modules/cloudsql-instance/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/cloudsql-instance/versions.tf
+++ b/modules/cloudsql-instance/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/compute-mig/README.md b/modules/compute-mig/README.md
index 9356e6ecd..8bdb23386 100644
--- a/modules/compute-mig/README.md
+++ b/modules/compute-mig/README.md
@@ -243,9 +243,9 @@ module "nginx-mig" {
target_size = 3
instance_template = module.nginx-template.template.self_link
update_policy = {
- minimal_action = "REPLACE"
- type = "PROACTIVE"
- min_ready_sec = 30
+ minimal_action = "REPLACE"
+ type = "PROACTIVE"
+ min_ready_sec = 30
max_surge = {
fixed = 1
}
@@ -321,7 +321,7 @@ module "nginx-mig" {
}
}
stateful_disks = {
- repd-1 = null
+ repd-1 = false
}
}
# tftest modules=2 resources=3
@@ -393,8 +393,8 @@ module "nginx-mig" {
stateful_config = {
# name needs to match a MIG instance name
instance-1 = {
- minimal_action = "NONE",
- most_disruptive_allowed_action = "REPLACE"
+ minimal_action = "NONE",
+ most_disruptive_allowed_action = "REPLACE"
preserved_state = {
disks = {
persistent-disk-1 = {
@@ -417,10 +417,10 @@ module "nginx-mig" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [instance_template](variables.tf#L174) | Instance template for the default version. | string | ✓ | |
-| [location](variables.tf#L179) | Compute zone or region. | string | ✓ | |
-| [name](variables.tf#L184) | Managed group name. | string | ✓ | |
-| [project_id](variables.tf#L195) | Project id. | string | ✓ | |
+| [instance_template](variables.tf#L177) | Instance template for the default version. | string | ✓ | |
+| [location](variables.tf#L182) | Compute zone or region. | string | ✓ | |
+| [name](variables.tf#L187) | Managed group name. | string | ✓ | |
+| [project_id](variables.tf#L198) | Project id. | string | ✓ | |
| [all_instances_config](variables.tf#L17) | Metadata and labels set to all instances in the group. | object({…}) | | null |
| [auto_healing_policies](variables.tf#L26) | Auto-healing policies for this group. | object({…}) | | null |
| [autoscaler_config](variables.tf#L35) | Optional autoscaler configuration. | object({…}) | | null |
@@ -428,14 +428,14 @@ module "nginx-mig" {
| [description](variables.tf#L89) | Optional description used for all resources managed by this module. | string | | "Terraform managed." |
| [distribution_policy](variables.tf#L95) | DIstribution policy for regional MIG. | object({…}) | | null |
| [health_check_config](variables.tf#L104) | Optional auto-created health check configuration, use the output self-link to set it in the auto healing policy. Refer to examples for usage. | object({…}) | | null |
-| [named_ports](variables.tf#L189) | Named ports. | map(number) | | null |
-| [stateful_config](variables.tf#L200) | Stateful configuration for individual instances. | map(object({…})) | | {} |
-| [stateful_disks](variables.tf#L219) | Stateful disk configuration applied at the MIG level to all instances, in device name => on permanent instance delete rule as boolean. | map(bool) | | {} |
-| [target_pools](variables.tf#L226) | Optional list of URLs for target pools to which new instances in the group are added. | list(string) | | [] |
-| [target_size](variables.tf#L232) | Group target size, leave null when using an autoscaler. | number | | null |
-| [update_policy](variables.tf#L238) | Update policy. Minimal action and type are required. | object({…}) | | null |
-| [versions](variables.tf#L259) | Additional application versions, target_size is optional. | map(object({…})) | | {} |
-| [wait_for_instances](variables.tf#L272) | Wait for all instances to be created/updated before returning. | object({…}) | | null |
+| [named_ports](variables.tf#L192) | Named ports. | map(number) | | null |
+| [stateful_config](variables.tf#L203) | Stateful configuration for individual instances. | map(object({…})) | | {} |
+| [stateful_disks](variables.tf#L222) | Stateful disk configuration applied at the MIG level to all instances, in device name => on permanent instance delete rule as boolean. | map(bool) | | {} |
+| [target_pools](variables.tf#L229) | Optional list of URLs for target pools to which new instances in the group are added. | list(string) | | [] |
+| [target_size](variables.tf#L235) | Group target size, leave null when using an autoscaler. | number | | null |
+| [update_policy](variables.tf#L241) | Update policy. Minimal action and type are required. | object({…}) | | null |
+| [versions](variables.tf#L262) | Additional application versions, target_size is optional. | map(object({…})) | | {} |
+| [wait_for_instances](variables.tf#L275) | Wait for all instances to be created/updated before returning. | object({…}) | | null |
## Outputs
diff --git a/modules/compute-mig/main.tf b/modules/compute-mig/main.tf
index 35f255a68..65ce55b8e 100644
--- a/modules/compute-mig/main.tf
+++ b/modules/compute-mig/main.tf
@@ -71,7 +71,7 @@ resource "google_compute_instance_group_manager" "default" {
for_each = var.stateful_disks
content {
device_name = stateful_disk.key
- delete_rule = stateful_disk.value
+ delete_rule = stateful_disk.value ? "ON_PERMANENT_INSTANCE_DELETION" : "NEVER"
}
}
@@ -161,7 +161,7 @@ resource "google_compute_region_instance_group_manager" "default" {
for_each = var.stateful_disks
content {
device_name = stateful_disk.key
- delete_rule = stateful_disk.value
+ delete_rule = stateful_disk.value ? "ON_PERMANENT_INSTANCE_DELETION" : "NEVER"
}
}
diff --git a/modules/compute-mig/variables.tf b/modules/compute-mig/variables.tf
index ecddd6687..30f2ce965 100644
--- a/modules/compute-mig/variables.tf
+++ b/modules/compute-mig/variables.tf
@@ -165,7 +165,10 @@ variable "health_check_config" {
condition = (
(try(var.health_check_config.grpc, null) == null ? 0 : 1) +
(try(var.health_check_config.http, null) == null ? 0 : 1) +
- (try(var.health_check_config.tcp, null) == null ? 0 : 1) <= 1
+ (try(var.health_check_config.http2, null) == null ? 0 : 1) +
+ (try(var.health_check_config.https, null) == null ? 0 : 1) +
+ (try(var.health_check_config.tcp, null) == null ? 0 : 1) +
+ (try(var.health_check_config.ssl, null) == null ? 0 : 1) <= 1
)
error_message = "Only one health check type can be configured at a time."
}
diff --git a/modules/compute-mig/versions.tf b/modules/compute-mig/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/compute-mig/versions.tf
+++ b/modules/compute-mig/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/compute-vm/README.md b/modules/compute-vm/README.md
index 3cf99403f..7cfaf068f 100644
--- a/modules/compute-vm/README.md
+++ b/modules/compute-vm/README.md
@@ -8,6 +8,23 @@ This module can operate in two distinct modes:
In both modes, an optional service account can be created and assigned to either instances or template. If you need a managed instance group when using the module in template mode, refer to the [`compute-mig`](../compute-mig) module.
## Examples
+- [Instance using defaults](#instance-using-defaults)
+- [Service account management](#service-account-management)
+- [Disk management](#disk-management)
+ - [Disk sources](#disk-sources)
+ - [Disk types and options](#disk-types-and-options)
+- [Network interfaces](#network-interfaces)
+ - [Internal and external IPs](#internal-and-external-ips)
+ - [Using Alias IPs](#using-alias-ips)
+ - [Using gVNIC](#using-gvnic)
+- [Metadata](#metadata)
+- [IAM](#iam)
+- [Spot VM](#spot-vm)
+- [Confidential compute](#confidential-compute)
+- [Disk encryption with Cloud KMS](#disk-encryption-with-cloud-kms)
+- [Instance template](#instance-template)
+- [Instance group](#instance-group)
+
### Instance using defaults
@@ -25,47 +42,73 @@ module "simple-vm-example" {
}]
service_account_create = true
}
-# tftest modules=1 resources=2
-
+# tftest modules=1 resources=2 inventory=simple.yaml
```
-### Spot VM
+### Service account management
-[Spot VMs](https://cloud.google.com/compute/docs/instances/spot) are ephemeral compute instances suitable for batch jobs and fault-tolerant workloads. Spot VMs provide new features that [preemptible instances](https://cloud.google.com/compute/docs/instances/preemptible) do not support, such as the absence of a maximum runtime.
+VM service accounts can be managed in three different ways:
+- You can let the module create a service account for you by settting `service_account_create = true`
+- You can use an existing service account by setting `service_account_create = false` (the default value) and passing the full email address of the service account to the `service_account` variable. This is useful, for example, if you want to reuse the service account from another previously created instance, or if you want to create the service account manually with the `iam-service-account` module. In this case, you probably also want to set `service_account_scopes` to `cloud-platform`.
+- Lastly, you can use the default compute service account by setting `service_account_crate = false`. Please note that using the default compute service account is not recommended.
```hcl
-module "spot-vm-example" {
+module "vm-managed-sa-example" {
source = "./fabric/modules/compute-vm"
project_id = var.project_id
zone = "europe-west1-b"
- name = "test"
- options = {
- spot = true
- termination_action = "STOP"
- }
+ name = "test1"
network_interfaces = [{
network = var.vpc.self_link
subnetwork = var.subnet.self_link
}]
service_account_create = true
}
-# tftest modules=1 resources=2
+module "vm-managed-sa-example2" {
+ source = "./fabric/modules/compute-vm"
+ project_id = var.project_id
+ zone = "europe-west1-b"
+ name = "test2"
+ network_interfaces = [{
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ }]
+ service_account = module.vm-managed-sa-example.service_account_email
+ service_account_scopes = ["cloud-platform"]
+}
+
+# not recommended
+module "vm-default-sa-example2" {
+ source = "./fabric/modules/compute-vm"
+ project_id = var.project_id
+ zone = "europe-west1-b"
+ name = "test3"
+ network_interfaces = [{
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ }]
+ service_account_create = false
+}
+
+# tftest modules=3 resources=4 inventory=sas.yaml
```
-### Disk sources
+### Disk management
+
+#### Disk sources
Attached disks can be created and optionally initialized from a pre-existing source, or attached to VMs when pre-existing. The `source` and `source_type` attributes of the `attached_disks` variable allows several modes of operation:
-- `source_type = "image"` can be used with zonal disks in instances and templates, set `source` to the image name or link
-- `source_type = "snapshot"` can be used with instances only, set `source` to the snapshot name or link
-- `source_type = "attach"` can be used for both instances and templates to attach an existing disk, set source to the name (for zonal disks) or link (for regional disks) of the existing disk to attach; no disk will be created
+- `source_type = "image"` can be used with zonal disks in instances and templates, set `source` to the image name or self link
+- `source_type = "snapshot"` can be used with instances only, set `source` to the snapshot name or self link
+- `source_type = "attach"` can be used for both instances and templates to attach an existing disk, set source to the name (for zonal disks) or self link (for regional disks) of the existing disk to attach; no disk will be created
- `source_type = null` can be used where an empty disk is needed, `source` becomes irrelevant and can be left null
This is an example of attaching a pre-existing regional PD to a new instance:
```hcl
-module "simple-vm-example" {
+module "vm-disks-example" {
source = "./fabric/modules/compute-vm"
project_id = var.project_id
zone = "${var.region}-b"
@@ -91,7 +134,7 @@ module "simple-vm-example" {
And the same example for an instance template (where not using the full self link of the disk triggers recreation of the template)
```hcl
-module "simple-vm-example" {
+module "vm-disks-example" {
source = "./fabric/modules/compute-vm"
project_id = var.project_id
zone = "${var.region}-b"
@@ -110,44 +153,87 @@ module "simple-vm-example" {
}
}]
service_account_create = true
- create_template = true
+ create_template = true
}
# tftest modules=1 resources=2
```
-### Disk encryption with Cloud KMS
+#### Disk types and options
-This example shows how to control disk encryption via the the `encryption` variable, in this case the self link to a KMS CryptoKey that will be used to encrypt boot and attached disk. Managing the key with the `../kms` module is of course possible, but is not shown here.
+The `attached_disks` variable exposes an `option` attribute that can be used to fine tune the configuration of each disk. The following example shows a VM with multiple disks
```hcl
-module "kms-vm-example" {
+module "vm-disk-options-example" {
source = "./fabric/modules/compute-vm"
project_id = var.project_id
zone = "europe-west1-b"
- name = "kms-test"
+ name = "test"
network_interfaces = [{
network = var.vpc.self_link
subnetwork = var.subnet.self_link
}]
attached_disks = [
{
- name = "attached-disk"
- size = 10
+ name = "data1"
+ size = "10"
+ source_type = "image"
+ source = "image-1"
+ options = {
+ auto_delete = false
+ replica_zone = "europe-west1-c"
+ }
+ },
+ {
+ name = "data2"
+ size = "20"
+ source_type = "snapshot"
+ source = "snapshot-2"
+ options = {
+ type = "pd-ssd"
+ mode = "READ_ONLY"
+ }
}
]
service_account_create = true
- boot_disk = {
- image = "projects/debian-cloud/global/images/family/debian-10"
- }
- encryption = {
- encrypt_boot = true
- kms_key_self_link = var.kms_key.self_link
- }
}
-# tftest modules=1 resources=3
+# tftest modules=1 resources=4 inventory=disk-options.yaml
```
-### Using Alias IPs
+### Network interfaces
+
+#### Internal and external IPs
+
+By default VNs are create with an automatically assigned IP addresses, but you can change it through the `addreses` and `nat` attributes of the `network_interfaces` variable:
+
+```hcl
+module "vm-internal-ip" {
+ source = "./fabric/modules/compute-vm"
+ project_id = "my-project"
+ zone = "europe-west1-b"
+ name = "vm-internal-ip"
+ network_interfaces = [{
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ addresses = { external = null, internal = "10.0.0.2" }
+ }]
+}
+
+module "vm-external-ip" {
+ source = "./fabric/modules/compute-vm"
+ project_id = "my-project"
+ zone = "europe-west1-b"
+ name = "vm-external-ip"
+ network_interfaces = [{
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ nat = true
+ addresses = { external = "8.8.8.8", internal = null }
+ }]
+}
+# tftest modules=2 resources=2 inventory=ips.yaml
+```
+
+#### Using Alias IPs
This example shows how to add additional [Alias IPs](https://cloud.google.com/vpc/docs/alias-ip) to your VM.
@@ -164,21 +250,20 @@ module "vm-with-alias-ips" {
alias1 = "10.16.0.10/32"
}
}]
- service_account_create = true
}
-# tftest modules=1 resources=2
+# tftest modules=1 resources=1 inventory=alias-ips.yaml
```
-### Using gVNIC
+#### Using gVNIC
This example shows how to enable [gVNIC](https://cloud.google.com/compute/docs/networking/using-gvnic) on your VM by customizing a `cos` image. Given that gVNIC needs to be enabled as an instance configuration and as a guest os configuration, you'll need to supply a bootable disk with `guest_os_features=GVNIC`. `SEV_CAPABLE`, `UEFI_COMPATIBLE` and `VIRTIO_SCSI_MULTIQUEUE` are enabled implicitly in the `cos`, `rhel`, `centos` and other images.
```hcl
resource "google_compute_image" "cos-gvnic" {
- project = "my-project"
- name = "my-image"
- source_image = "https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-89-16108-534-18"
+ project = "my-project"
+ name = "my-image"
+ source_image = "https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-89-16108-534-18"
guest_os_features {
type = "GVNIC"
@@ -200,8 +285,8 @@ module "vm-with-gvnic" {
zone = "europe-west1-b"
name = "test"
boot_disk = {
- image = google_compute_image.cos-gvnic.self_link
- type = "pd-ssd"
+ image = google_compute_image.cos-gvnic.self_link
+ type = "pd-ssd"
}
network_interfaces = [{
network = var.vpc.self_link
@@ -210,9 +295,151 @@ module "vm-with-gvnic" {
}]
service_account_create = true
}
-# tftest modules=1 resources=3
+# tftest modules=1 resources=3 inventory=gvnic.yaml
```
+### Metadata
+
+You can define labels and custom metadata values. Metadata can be leveraged, for example, to define a custom startup script.
+
+```hcl
+module "vm-metadata-example" {
+ source = "./fabric/modules/compute-vm"
+ project_id = var.project_id
+ zone = "europe-west1-b"
+ name = "nginx-server"
+ network_interfaces = [{
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ }]
+ labels = {
+ env = "dev"
+ system = "crm"
+ }
+ metadata = {
+ startup-script = <<-EOF
+ #! /bin/bash
+ apt-get update
+ apt-get install -y nginx
+ EOF
+ }
+ service_account_create = true
+}
+# tftest modules=1 resources=2 inventory=metadata.yaml
+```
+
+### IAM
+
+Like most modules, you can assign IAM roles to the instance using the `iam` variable.
+
+```hcl
+module "vm-iam-example" {
+ source = "./fabric/modules/compute-vm"
+ project_id = var.project_id
+ zone = "europe-west1-b"
+ name = "webserver"
+ network_interfaces = [{
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ }]
+ iam = {
+ "roles/compute.instanceAdmin" = [
+ "group:webserver@example.com",
+ "group:admin@example.com"
+ ]
+ }
+}
+# tftest modules=1 resources=2 inventory=iam.yaml
+
+```
+
+### Spot VM
+
+[Spot VMs](https://cloud.google.com/compute/docs/instances/spot) are ephemeral compute instances suitable for batch jobs and fault-tolerant workloads. Spot VMs provide new features that [preemptible instances](https://cloud.google.com/compute/docs/instances/preemptible) do not support, such as the absence of a maximum runtime.
+
+```hcl
+module "spot-vm-example" {
+ source = "./fabric/modules/compute-vm"
+ project_id = var.project_id
+ zone = "europe-west1-b"
+ name = "test"
+ options = {
+ spot = true
+ termination_action = "STOP"
+ }
+ network_interfaces = [{
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ }]
+}
+# tftest modules=1 resources=1 inventory=spot.yaml
+```
+
+### Confidential compute
+
+You can enable confidential compute with the `confidential_compute` variable, which can be used for standalone instances or for instance templates.
+
+```hcl
+module "vm-confidential-example" {
+ source = "./fabric/modules/compute-vm"
+ project_id = var.project_id
+ zone = "europe-west1-b"
+ name = "confidential-vm"
+ confidential_compute = true
+ network_interfaces = [{
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ }]
+
+}
+
+module "template-confidential-example" {
+ source = "./fabric/modules/compute-vm"
+ project_id = var.project_id
+ zone = "europe-west1-b"
+ name = "confidential-template"
+ confidential_compute = true
+ create_template = true
+ network_interfaces = [{
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ }]
+}
+
+# tftest modules=2 resources=2 inventory=confidential.yaml
+```
+
+### Disk encryption with Cloud KMS
+
+This example shows how to control disk encryption via the the `encryption` variable, in this case the self link to a KMS CryptoKey that will be used to encrypt boot and attached disk. Managing the key with the `../kms` module is of course possible, but is not shown here.
+
+```hcl
+module "kms-vm-example" {
+ source = "./fabric/modules/compute-vm"
+ project_id = var.project_id
+ zone = "europe-west1-b"
+ name = "kms-test"
+ network_interfaces = [{
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ }]
+ attached_disks = [{
+ name = "attached-disk"
+ size = 10
+ }]
+ service_account_create = true
+ boot_disk = {
+ image = "projects/debian-cloud/global/images/family/debian-10"
+ }
+ encryption = {
+ encrypt_boot = true
+ kms_key_self_link = var.kms_key.self_link
+ }
+}
+# tftest modules=1 resources=3 inventory=cmek.yaml
+```
+
+
### Instance template
This example shows how to use the module to manage an instance template that defines an additional attached disk for each instance, and overrides defaults for the boot disk image and service account.
@@ -239,7 +466,7 @@ module "cos-test" {
service_account = "vm-default@my-project.iam.gserviceaccount.com"
create_template = true
}
-# tftest modules=1 resources=1
+# tftest modules=1 resources=1 inventory=template.yaml
```
### Instance group
@@ -270,7 +497,7 @@ module "instance-group" {
}
group = { named_ports = {} }
}
-# tftest modules=1 resources=2
+# tftest modules=1 resources=2 inventory=group.yaml
```
@@ -278,34 +505,34 @@ module "instance-group" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L180) | Instance name. | string | ✓ | |
-| [network_interfaces](variables.tf#L185) | Network interfaces configuration. Use self links for Shared VPC, set addresses to null if not needed. | list(object({…})) | ✓ | |
-| [project_id](variables.tf#L222) | Project id. | string | ✓ | |
-| [zone](variables.tf#L281) | Compute zone. | string | ✓ | |
+| [name](variables.tf#L181) | Instance name. | string | ✓ | |
+| [network_interfaces](variables.tf#L186) | Network interfaces configuration. Use self links for Shared VPC, set addresses to null if not needed. | list(object({…})) | ✓ | |
+| [project_id](variables.tf#L223) | Project id. | string | ✓ | |
+| [zone](variables.tf#L282) | Compute zone. | string | ✓ | |
| [attached_disk_defaults](variables.tf#L17) | Defaults for attached disks options. | object({…}) | | {…} |
-| [attached_disks](variables.tf#L38) | Additional disks, if options is null defaults will be used in its place. Source type is one of 'image' (zonal disks in vms and template), 'snapshot' (vm), 'existing', and null. | list(object({…})) | | [] |
-| [boot_disk](variables.tf#L81) | Boot disk properties. | object({…}) | | {…} |
-| [can_ip_forward](variables.tf#L97) | Enable IP forwarding. | bool | | false |
-| [confidential_compute](variables.tf#L103) | Enable Confidential Compute for these instances. | bool | | false |
-| [create_template](variables.tf#L109) | Create instance template instead of instances. | bool | | false |
-| [description](variables.tf#L114) | Description of a Compute Instance. | string | | "Managed by the compute-vm Terraform module." |
-| [enable_display](variables.tf#L120) | Enable virtual display on the instances. | bool | | false |
-| [encryption](variables.tf#L126) | Encryption options. Only one of kms_key_self_link and disk_encryption_key_raw may be set. If needed, you can specify to encrypt or not the boot disk. | object({…}) | | null |
-| [group](variables.tf#L136) | Define this variable to create an instance group for instances. Disabled for template use. | object({…}) | | null |
-| [hostname](variables.tf#L144) | Instance FQDN name. | string | | null |
-| [iam](variables.tf#L150) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
-| [instance_type](variables.tf#L156) | Instance type. | string | | "f1-micro" |
-| [labels](variables.tf#L162) | Instance labels. | map(string) | | {} |
-| [metadata](variables.tf#L168) | Instance metadata. | map(string) | | {} |
-| [min_cpu_platform](variables.tf#L174) | Minimum CPU platform. | string | | null |
-| [options](variables.tf#L200) | Instance options. | object({…}) | | {…} |
-| [scratch_disks](variables.tf#L227) | Scratch disks configuration. | object({…}) | | {…} |
-| [service_account](variables.tf#L239) | Service account email. Unused if service account is auto-created. | string | | null |
-| [service_account_create](variables.tf#L245) | Auto-create service account. | bool | | false |
-| [service_account_scopes](variables.tf#L253) | Scopes applied to service account. | list(string) | | [] |
-| [shielded_config](variables.tf#L259) | Shielded VM configuration of the instances. | object({…}) | | null |
-| [tag_bindings](variables.tf#L269) | Tag bindings for this instance, in key => tag value id format. | map(string) | | null |
-| [tags](variables.tf#L275) | Instance network tags for firewall rule targets. | list(string) | | [] |
+| [attached_disks](variables.tf#L38) | Additional disks, if options is null defaults will be used in its place. Source type is one of 'image' (zonal disks in vms and template), 'snapshot' (vm), 'existing', and null. | list(object({…})) | | [] |
+| [boot_disk](variables.tf#L82) | Boot disk properties. | object({…}) | | {…} |
+| [can_ip_forward](variables.tf#L98) | Enable IP forwarding. | bool | | false |
+| [confidential_compute](variables.tf#L104) | Enable Confidential Compute for these instances. | bool | | false |
+| [create_template](variables.tf#L110) | Create instance template instead of instances. | bool | | false |
+| [description](variables.tf#L115) | Description of a Compute Instance. | string | | "Managed by the compute-vm Terraform module." |
+| [enable_display](variables.tf#L121) | Enable virtual display on the instances. | bool | | false |
+| [encryption](variables.tf#L127) | Encryption options. Only one of kms_key_self_link and disk_encryption_key_raw may be set. If needed, you can specify to encrypt or not the boot disk. | object({…}) | | null |
+| [group](variables.tf#L137) | Define this variable to create an instance group for instances. Disabled for template use. | object({…}) | | null |
+| [hostname](variables.tf#L145) | Instance FQDN name. | string | | null |
+| [iam](variables.tf#L151) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
+| [instance_type](variables.tf#L157) | Instance type. | string | | "f1-micro" |
+| [labels](variables.tf#L163) | Instance labels. | map(string) | | {} |
+| [metadata](variables.tf#L169) | Instance metadata. | map(string) | | {} |
+| [min_cpu_platform](variables.tf#L175) | Minimum CPU platform. | string | | null |
+| [options](variables.tf#L201) | Instance options. | object({…}) | | {…} |
+| [scratch_disks](variables.tf#L228) | Scratch disks configuration. | object({…}) | | {…} |
+| [service_account](variables.tf#L240) | Service account email. Unused if service account is auto-created. | string | | null |
+| [service_account_create](variables.tf#L246) | Auto-create service account. | bool | | false |
+| [service_account_scopes](variables.tf#L254) | Scopes applied to service account. | list(string) | | [] |
+| [shielded_config](variables.tf#L260) | Shielded VM configuration of the instances. | object({…}) | | null |
+| [tag_bindings](variables.tf#L270) | Tag bindings for this instance, in key => tag value id format. | map(string) | | null |
+| [tags](variables.tf#L276) | Instance network tags for firewall rule targets. | list(string) | | [] |
## Outputs
diff --git a/modules/compute-vm/main.tf b/modules/compute-vm/main.tf
index 32051555c..31e370672 100644
--- a/modules/compute-vm/main.tf
+++ b/modules/compute-vm/main.tf
@@ -17,7 +17,7 @@
locals {
attached_disks = {
for disk in var.attached_disks :
- disk.name => merge(disk, {
+ (disk.name != null ? disk.name : disk.device_name) => merge(disk, {
options = disk.options == null ? var.attached_disk_defaults : disk.options
})
}
@@ -138,7 +138,7 @@ resource "google_compute_instance" "default" {
for_each = local.attached_disks_zonal
iterator = config
content {
- device_name = config.value.name
+ device_name = config.value.device_name != null ? config.value.device_name : config.value.name
mode = config.value.options.mode
source = (
config.value.source_type == "attach"
@@ -152,7 +152,7 @@ resource "google_compute_instance" "default" {
for_each = local.attached_disks_regional
iterator = config
content {
- device_name = config.value.name
+ device_name = config.value.device_name != null ? config.value.device_name : config.value.name
mode = config.value.options.mode
source = (
config.value.source_type == "attach"
@@ -285,7 +285,7 @@ resource "google_compute_instance_template" "default" {
iterator = config
content {
auto_delete = config.value.options.auto_delete
- device_name = config.value.name
+ device_name = config.value.device_name != null ? config.value.device_name : config.value.name
# Cannot use `source` with any of the fields in
# [disk_size_gb disk_name disk_type source_image labels]
disk_type = (
diff --git a/modules/compute-vm/variables.tf b/modules/compute-vm/variables.tf
index 4287999fe..dc5651775 100644
--- a/modules/compute-vm/variables.tf
+++ b/modules/compute-vm/variables.tf
@@ -39,6 +39,7 @@ variable "attached_disks" {
description = "Additional disks, if options is null defaults will be used in its place. Source type is one of 'image' (zonal disks in vms and template), 'snapshot' (vm), 'existing', and null."
type = list(object({
name = string
+ device_name = optional(string)
size = string
source = optional(string)
source_type = optional(string)
diff --git a/modules/compute-vm/versions.tf b/modules/compute-vm/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/compute-vm/versions.tf
+++ b/modules/compute-vm/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/container-registry/versions.tf b/modules/container-registry/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/container-registry/versions.tf
+++ b/modules/container-registry/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/data-catalog-policy-tag/README.md b/modules/data-catalog-policy-tag/README.md
index b38ab77d5..3cf4aaafc 100644
--- a/modules/data-catalog-policy-tag/README.md
+++ b/modules/data-catalog-policy-tag/README.md
@@ -12,7 +12,7 @@ module "cmn-dc" {
source = "./fabric/modules/data-catalog-policy-tag"
name = "my-datacatalog-policy-tags"
project_id = "my-project"
- tags = {
+ tags = {
low = null, medium = null, high = null
}
}
@@ -26,10 +26,10 @@ module "cmn-dc" {
source = "./fabric/modules/data-catalog-policy-tag"
name = "my-datacatalog-policy-tags"
project_id = "my-project"
- tags = {
- low = null
+ tags = {
+ low = null
medium = null
- high = {"roles/datacatalog.categoryFineGrainedReader" = ["group:GROUP_NAME@example.com"]}
+ high = { "roles/datacatalog.categoryFineGrainedReader" = ["group:GROUP_NAME@example.com"] }
}
iam = {
"roles/datacatalog.categoryAdmin" = ["group:GROUP_NAME@example.com"]
diff --git a/modules/data-catalog-policy-tag/versions.tf b/modules/data-catalog-policy-tag/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/data-catalog-policy-tag/versions.tf
+++ b/modules/data-catalog-policy-tag/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/datafusion/README.md b/modules/datafusion/README.md
index 79115f050..377e81452 100644
--- a/modules/datafusion/README.md
+++ b/modules/datafusion/README.md
@@ -8,11 +8,11 @@ This module allows simple management of ['Google Data Fusion'](https://cloud.goo
```hcl
module "datafusion" {
- source = "./fabric/modules/datafusion"
- name = "my-datafusion"
- region = "europe-west1"
- project_id = "my-project"
- network = "my-network-name"
+ source = "./fabric/modules/datafusion"
+ name = "my-datafusion"
+ region = "europe-west1"
+ project_id = "my-project"
+ network = "my-network-name"
# TODO: remove the following line
firewall_create = false
}
diff --git a/modules/datafusion/versions.tf b/modules/datafusion/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/datafusion/versions.tf
+++ b/modules/datafusion/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/dns/README.md b/modules/dns/README.md
index 9e461f0e5..a405ff753 100644
--- a/modules/dns/README.md
+++ b/modules/dns/README.md
@@ -21,7 +21,7 @@ module "private-dns" {
"A myhost" = { ttl = 600, records = ["10.0.0.120"] }
}
}
-# tftest modules=1 resources=3
+# tftest modules=1 resources=3 inventory=private-zone.yaml
```
### Forwarding Zone
@@ -36,7 +36,7 @@ module "private-dns" {
client_networks = [var.vpc.self_link]
forwarders = { "10.0.1.1" = null, "1.2.3.4" = "private" }
}
-# tftest modules=1 resources=1
+# tftest modules=1 resources=1 inventory=forwarding-zone.yaml
```
### Peering Zone
@@ -47,11 +47,12 @@ module "private-dns" {
project_id = "myproject"
type = "peering"
name = "test-example"
- domain = "test.example."
+ domain = "."
+ description = "Forwarding zone for ."
client_networks = [var.vpc.self_link]
peer_network = var.vpc2.self_link
}
-# tftest modules=1 resources=1
+# tftest modules=1 resources=1 inventory=peering-zone.yaml
```
### Routing Policies
@@ -84,7 +85,7 @@ module "private-dns" {
}
}
}
-# tftest modules=1 resources=4
+# tftest modules=1 resources=4 inventory=routing-policies.yaml
```
### Reverse Lookup Zone
@@ -98,7 +99,23 @@ module "private-dns" {
domain = "0.0.10.in-addr.arpa."
client_networks = [var.vpc.self_link]
}
-# tftest modules=1 resources=1
+# tftest modules=1 resources=1 inventory=reverse-zone.yaml
+```
+
+### Public Zone
+
+```hcl
+module "public-dns" {
+ source = "./fabric/modules/dns"
+ project_id = "myproject"
+ type = "public"
+ name = "example"
+ domain = "example.com."
+ recordsets = {
+ "A myhost" = { ttl = 300, records = ["127.0.0.1"] }
+ }
+}
+# tftest modules=1 resources=3 inventory=public-zone.yaml
```
diff --git a/modules/dns/main.tf b/modules/dns/main.tf
index ca30c7d0c..edf342ef6 100644
--- a/modules/dns/main.tf
+++ b/modules/dns/main.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/modules/dns/versions.tf b/modules/dns/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/dns/versions.tf
+++ b/modules/dns/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/endpoints/README.md b/modules/endpoints/README.md
index 4e6fd2eb9..3b9e317db 100644
--- a/modules/endpoints/README.md
+++ b/modules/endpoints/README.md
@@ -22,7 +22,7 @@ module "endpoint" {
```
```yaml
-# tftest file openapi configs/endpoints/openapi.yaml
+# tftest-file id=openapi path=configs/endpoints/openapi.yaml
swagger: "2.0"
info:
description: "A simple Google Cloud Endpoints API example."
diff --git a/modules/endpoints/versions.tf b/modules/endpoints/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/endpoints/versions.tf
+++ b/modules/endpoints/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/folder/README.md b/modules/folder/README.md
index 5120d2467..e1ad6809e 100644
--- a/modules/folder/README.md
+++ b/modules/folder/README.md
@@ -2,29 +2,36 @@
This module allows the creation and management of folders, including support for IAM bindings, organization policies, and hierarchical firewall rules.
-## Examples
-
-### IAM bindings
+## Basic example with IAM bindings
```hcl
module "folder" {
source = "./fabric/modules/folder"
parent = "organizations/1234567890"
- name = "Folder name"
- group_iam = {
+ name = "Folder name"
+ group_iam = {
"cloud-owners@example.org" = [
- "roles/owner",
- "roles/resourcemanager.projectCreator"
+ "roles/owner",
+ "roles/resourcemanager.folderAdmin",
+ "roles/resourcemanager.projectCreator"
]
}
iam = {
- "roles/owner" = ["user:one@example.com"]
+ "roles/owner" = ["user:one@example.org"]
+ }
+ iam_additive = {
+ "roles/compute.admin" = ["user:a1@example.org", "user:a2@example.org"]
+ "roles/compute.viewer" = ["user:a2@example.org"]
+ }
+ iam_additive_members = {
+ "user:am1@example.org" = ["roles/storage.admin"]
+ "user:am2@example.org" = ["roles/storage.objectViewer"]
}
}
-# tftest modules=1 resources=3
+# tftest modules=1 resources=9 inventory=iam.yaml
```
-### Organization policies
+## Organization policies
To manage organization policies, the `orgpolicy.googleapis.com` service should be enabled in the quota project.
@@ -32,7 +39,7 @@ To manage organization policies, the `orgpolicy.googleapis.com` service should b
module "folder" {
source = "./fabric/modules/folder"
parent = "organizations/1234567890"
- name = "Folder name"
+ name = "Folder name"
org_policies = {
"compute.disableGuestAttributesAccess" = {
enforce = true
@@ -72,70 +79,14 @@ module "folder" {
}
}
}
-# tftest modules=1 resources=8
+# tftest modules=1 resources=8 inventory=org-policies.yaml
```
### Organization policy factory
See the [organization policy factory in the project module](../project#organization-policy-factory).
-### Firewall policy factory
-
-In the same way as for the [organization](../organization) module, the in-built factory allows you to define a single policy, using one file for rules, and an optional file for CIDR range substitution variables. Remember that non-absolute paths are relative to the root module (the folder where you run `terraform`).
-
-```hcl
-module "folder" {
- source = "./fabric/modules/folder"
- parent = "organizations/1234567890"
- name = "Folder name"
- firewall_policy_factory = {
- cidr_file = "configs/firewall-policies/cidrs.yaml"
- policy_name = null
- rules_file = "configs/firewall-policies/rules.yaml"
- }
- firewall_policy_association = {
- factory-policy = module.folder.firewall_policy_id["factory"]
- }
-}
-# tftest modules=1 resources=5 files=cidrs,rules
-```
-
-```yaml
-# tftest file cidrs configs/firewall-policies/cidrs.yaml
-rfc1918:
- - 10.0.0.0/8
- - 172.16.0.0/12
- - 192.168.0.0/16
-```
-
-```yaml
-# tftest file rules configs/firewall-policies/rules.yaml
-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-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
-```
-
-### Logging Sinks
+## Logging Sinks
```hcl
module "gcs" {
@@ -164,7 +115,6 @@ module "bucket" {
id = "bucket"
}
-
module "folder-sink" {
source = "./fabric/modules/folder"
parent = "folders/657104291943"
@@ -198,10 +148,19 @@ module "folder-sink" {
no-gce-instances = "resource.type=gce_instance"
}
}
-# tftest modules=5 resources=14
+# tftest modules=5 resources=14 inventory=logging.yaml
```
-### Hierarchical firewall policies
+## Hierarchical firewall policies
+
+Hierarchical firewall policies can be managed in two ways:
+
+- via the `firewall_policies` variable, to directly define policies and rules in Terraform
+- via the `firewall_policy_factory` variable, to leverage external YaML files via a simple "factory" embedded in the module ([see here](../../blueprints/factories) for more context on factories)
+
+Once you have policies (either created via the module or externally), you can associate them using the `firewall_policy_association` variable.
+
+### Directly defined firewall policies
```hcl
module "folder1" {
@@ -211,6 +170,17 @@ module "folder1" {
firewall_policies = {
iap-policy = {
+ allow-admins = {
+ description = "Access from the admin subnet to all subnets"
+ direction = "INGRESS"
+ action = "allow"
+ priority = 1000
+ ranges = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
+ ports = { all = [] }
+ target_service_accounts = null
+ target_resources = null
+ logging = false
+ }
allow-iap-ssh = {
description = "Always allow ssh from IAP"
direction = "INGRESS"
@@ -237,7 +207,71 @@ module "folder2" {
iap-policy = module.folder1.firewall_policy_id["iap-policy"]
}
}
-# tftest modules=2 resources=6
+# tftest modules=2 resources=7 inventory=hfw.yaml
+```
+### Firewall policy factory
+
+The in-built factory allows you to define a single policy, using one file for rules, and an optional file for CIDR range substitution variables. Remember that non-absolute paths are relative to the root module (the folder where you run `terraform`).
+
+```hcl
+module "folder1" {
+ source = "./fabric/modules/folder"
+ parent = var.organization_id
+ name = "policy-container"
+ firewall_policy_factory = {
+ cidr_file = "configs/firewall-policies/cidrs.yaml"
+ policy_name = "iap-policy"
+ rules_file = "configs/firewall-policies/rules.yaml"
+ }
+ firewall_policy_association = {
+ iap-policy = "iap-policy"
+ }
+}
+
+module "folder2" {
+ source = "./fabric/modules/folder"
+ parent = var.organization_id
+ name = "hf2"
+ firewall_policy_association = {
+ iap-policy = module.folder1.firewall_policy_id["iap-policy"]
+ }
+}
+# tftest modules=2 resources=7 files=cidrs,rules inventory=hfw.yaml
+```
+
+```yaml
+# tftest-file id=cidrs path=configs/firewall-policies/cidrs.yaml
+rfc1918:
+ - 10.0.0.0/8
+ - 172.16.0.0/12
+ - 192.168.0.0/16
+```
+
+```yaml
+# tftest-file id=rules path=configs/firewall-policies/rules.yaml
+allow-admins:
+ description: Access from the admin subnet to all subnets
+ direction: INGRESS
+ action: allow
+ priority: 1000
+ ranges:
+ - $rfc1918
+ ports:
+ all: []
+ target_resources: null
+ logging: false
+
+allow-iap-ssh:
+ description: "Always allow ssh from IAP"
+ direction: INGRESS
+ action: allow
+ priority: 100
+ ranges:
+ - 35.235.240.0/20
+ ports:
+ tcp: ["22"]
+ target_resources: null
+ logging: false
```
## Tags
@@ -250,8 +284,8 @@ module "org" {
organization_id = var.organization_id
tags = {
environment = {
- description = "Environment specification."
- iam = null
+ description = "Environment specification."
+ iam = null
values = {
dev = null
prod = null
@@ -269,7 +303,7 @@ module "folder" {
foo = "tagValues/12345678"
}
}
-# tftest modules=2 resources=6
+# tftest modules=2 resources=6 inventory=tags.yaml
```
diff --git a/modules/folder/organization-policies.tf b/modules/folder/organization-policies.tf
index 999d1c586..47532f21b 100644
--- a/modules/folder/organization-policies.tf
+++ b/modules/folder/organization-policies.tf
@@ -95,23 +95,6 @@ resource "google_org_policy_policy" "default" {
inherit_from_parent = each.value.inherit_from_parent
reset = each.value.reset
- rules {
- allow_all = try(each.value.allow.all, null) == true ? "TRUE" : null
- deny_all = try(each.value.deny.all, null) == true ? "TRUE" : null
- enforce = (
- each.value.is_boolean_policy && each.value.enforce != null
- ? upper(tostring(each.value.enforce))
- : null
- )
- dynamic "values" {
- for_each = each.value.has_values ? [1] : []
- content {
- allowed_values = try(each.value.allow.values, null)
- denied_values = try(each.value.deny.values, null)
- }
- }
- }
-
dynamic "rules" {
for_each = each.value.rules
iterator = rule
@@ -138,5 +121,22 @@ resource "google_org_policy_policy" "default" {
}
}
}
+
+ rules {
+ allow_all = try(each.value.allow.all, null) == true ? "TRUE" : null
+ deny_all = try(each.value.deny.all, null) == true ? "TRUE" : null
+ enforce = (
+ each.value.is_boolean_policy && each.value.enforce != null
+ ? upper(tostring(each.value.enforce))
+ : null
+ )
+ dynamic "values" {
+ for_each = each.value.has_values ? [1] : []
+ content {
+ allowed_values = try(each.value.allow.values, null)
+ denied_values = try(each.value.deny.values, null)
+ }
+ }
+ }
}
}
diff --git a/modules/folder/versions.tf b/modules/folder/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/folder/versions.tf
+++ b/modules/folder/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/gcs/README.md b/modules/gcs/README.md
index 7e6cc22f4..07c5a6d7b 100644
--- a/modules/gcs/README.md
+++ b/modules/gcs/README.md
@@ -1,4 +1,5 @@
# Google Cloud Storage Module
+
## Example
```hcl
@@ -7,52 +8,46 @@ module "bucket" {
project_id = "myproject"
prefix = "test"
name = "my-bucket"
+ versioning = true
iam = {
"roles/storage.admin" = ["group:storage@example.com"]
}
+ labels = {
+ cost-center = "devops"
+ }
}
-# tftest modules=1 resources=2
+# tftest modules=1 resources=2 inventory=simple.yaml
```
### Example with Cloud KMS
```hcl
module "bucket" {
- source = "./fabric/modules/gcs"
- project_id = "myproject"
- prefix = "test"
- name = "my-bucket"
- iam = {
- "roles/storage.admin" = ["group:storage@example.com"]
- }
+ source = "./fabric/modules/gcs"
+ project_id = "myproject"
+ name = "my-bucket"
encryption_key = "my-encryption-key"
}
-# tftest modules=1 resources=2
+# tftest modules=1 resources=1 inventory=cmek.yaml
```
-### Example with retention policy
+### Example with retention policy and logging
```hcl
module "bucket" {
source = "./fabric/modules/gcs"
project_id = "myproject"
- prefix = "test"
name = "my-bucket"
- iam = {
- "roles/storage.admin" = ["group:storage@example.com"]
- }
-
retention_policy = {
retention_period = 100
is_locked = true
}
-
logging_config = {
- log_bucket = var.bucket
+ log_bucket = "log-bucket"
log_object_prefix = null
}
}
-# tftest modules=1 resources=2
+# tftest modules=1 resources=1 inventory=retention-logging.yaml
```
### Example with lifecycle rule
@@ -61,39 +56,28 @@ module "bucket" {
module "bucket" {
source = "./fabric/modules/gcs"
project_id = "myproject"
- prefix = "test"
- name = "my-bucket"
-
- iam = {
- "roles/storage.admin" = ["group:storage@example.com"]
- }
-
- lifecycle_rule = {
- action = {
- type = "SetStorageClass"
- storage_class = "STANDARD"
- }
- condition = {
- age = 30
- created_before = null
- with_state = null
- matches_storage_class = null
- num_newer_versions = null
- custom_time_before = null
- days_since_custom_time = null
- days_since_noncurrent_time = null
- noncurrent_time_before = null
+ name = "my-bucket"
+ lifecycle_rules = {
+ lr-0 = {
+ action = {
+ type = "SetStorageClass"
+ storage_class = "STANDARD"
+ }
+ condition = {
+ age = 30
+ }
}
}
}
-# tftest modules=1 resources=2
+# tftest modules=1 resources=1 inventory=lifecycle.yaml
```
+
### Minimal example with GCS notifications
+
```hcl
module "bucket-gcs-notification" {
source = "./fabric/modules/gcs"
project_id = "myproject"
- prefix = "test"
name = "my-bucket"
notification_config = {
enabled = true
@@ -104,7 +88,7 @@ module "bucket-gcs-notification" {
custom_attributes = {}
}
}
-# tftest modules=1 resources=4
+# tftest modules=1 resources=4 inventory=notification.yaml
```
@@ -112,23 +96,23 @@ module "bucket-gcs-notification" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L89) | Bucket name suffix. | string | ✓ | |
-| [project_id](variables.tf#L117) | Bucket project id. | string | ✓ | |
-| [cors](variables.tf#L17) | CORS configuration for the bucket. Defaults to null. | object({…}) | | null |
+| [name](variables.tf#L116) | Bucket name suffix. | string | ✓ | |
+| [project_id](variables.tf#L145) | Bucket project id. | string | ✓ | |
+| [cors](variables.tf#L17) | CORS configuration for the bucket. Defaults to null. | object({…}) | | null |
| [encryption_key](variables.tf#L28) | KMS key that will be used for encryption. | string | | null |
| [force_destroy](variables.tf#L34) | Optional map to set force destroy keyed by name, defaults to false. | bool | | false |
| [iam](variables.tf#L40) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
| [labels](variables.tf#L46) | Labels to be attached to all buckets. | map(string) | | {} |
-| [lifecycle_rule](variables.tf#L52) | Bucket lifecycle rule. | object({…}) | | null |
-| [location](variables.tf#L74) | Bucket location. | string | | "EU" |
-| [logging_config](variables.tf#L80) | Bucket logging configuration. | object({…}) | | null |
-| [notification_config](variables.tf#L94) | GCS Notification configuration. | object({…}) | | null |
-| [prefix](variables.tf#L107) | Optional prefix used to generate the bucket name. | string | | null |
-| [retention_policy](variables.tf#L122) | Bucket retention policy. | object({…}) | | null |
-| [storage_class](variables.tf#L131) | Bucket storage class. | string | | "MULTI_REGIONAL" |
-| [uniform_bucket_level_access](variables.tf#L141) | Allow using object ACLs (false) or not (true, this is the recommended behavior) , defaults to true (which is the recommended practice, but not the behavior of storage API). | bool | | true |
-| [versioning](variables.tf#L147) | Enable versioning, defaults to false. | bool | | false |
-| [website](variables.tf#L153) | Bucket website. | object({…}) | | null |
+| [lifecycle_rules](variables.tf#L52) | Bucket lifecycle rule. | map(object({…})) | | {} |
+| [location](variables.tf#L101) | Bucket location. | string | | "EU" |
+| [logging_config](variables.tf#L107) | Bucket logging configuration. | object({…}) | | null |
+| [notification_config](variables.tf#L121) | GCS Notification configuration. | object({…}) | | null |
+| [prefix](variables.tf#L135) | Optional prefix used to generate the bucket name. | string | | null |
+| [retention_policy](variables.tf#L150) | Bucket retention policy. | object({…}) | | null |
+| [storage_class](variables.tf#L159) | Bucket storage class. | string | | "MULTI_REGIONAL" |
+| [uniform_bucket_level_access](variables.tf#L169) | Allow using object ACLs (false) or not (true, this is the recommended behavior) , defaults to true (which is the recommended practice, but not the behavior of storage API). | bool | | true |
+| [versioning](variables.tf#L175) | Enable versioning, defaults to false. | bool | | false |
+| [website](variables.tf#L181) | Bucket website. | object({…}) | | null |
## Outputs
diff --git a/modules/gcs/main.tf b/modules/gcs/main.tf
index 960b23d2e..68b77077d 100644
--- a/modules/gcs/main.tf
+++ b/modules/gcs/main.tf
@@ -75,22 +75,25 @@ resource "google_storage_bucket" "bucket" {
}
dynamic "lifecycle_rule" {
- for_each = var.lifecycle_rule == null ? [] : [""]
+ for_each = var.lifecycle_rules
+ iterator = rule
content {
action {
- type = var.lifecycle_rule.action["type"]
- storage_class = var.lifecycle_rule.action["storage_class"]
+ type = rule.value.action.type
+ storage_class = rule.value.action.storage_class
}
condition {
- age = var.lifecycle_rule.condition["age"]
- created_before = var.lifecycle_rule.condition["created_before"]
- with_state = var.lifecycle_rule.condition["with_state"]
- matches_storage_class = var.lifecycle_rule.condition["matches_storage_class"]
- num_newer_versions = var.lifecycle_rule.condition["num_newer_versions"]
- custom_time_before = var.lifecycle_rule.condition["custom_time_before"]
- days_since_custom_time = var.lifecycle_rule.condition["days_since_custom_time"]
- days_since_noncurrent_time = var.lifecycle_rule.condition["days_since_noncurrent_time"]
- noncurrent_time_before = var.lifecycle_rule.condition["noncurrent_time_before"]
+ age = rule.value.condition.age
+ created_before = rule.value.condition.created_before
+ custom_time_before = rule.value.condition.custom_time_before
+ days_since_custom_time = rule.value.condition.days_since_custom_time
+ days_since_noncurrent_time = rule.value.condition.days_since_noncurrent_time
+ matches_prefix = rule.value.condition.matches_prefix
+ matches_storage_class = rule.value.condition.matches_storage_class
+ matches_suffix = rule.value.condition.matches_suffix
+ noncurrent_time_before = rule.value.condition.noncurrent_time_before
+ num_newer_versions = rule.value.condition.num_newer_versions
+ with_state = rule.value.condition.with_state
}
}
}
@@ -104,15 +107,14 @@ resource "google_storage_bucket_iam_binding" "bindings" {
}
resource "google_storage_notification" "notification" {
- count = local.notification ? 1 : 0
- bucket = google_storage_bucket.bucket.name
- payload_format = var.notification_config.payload_format
- topic = google_pubsub_topic.topic[0].id
- event_types = var.notification_config.event_types
- custom_attributes = var.notification_config.custom_attributes
-
- depends_on = [google_pubsub_topic_iam_binding.binding]
-
+ count = local.notification ? 1 : 0
+ bucket = google_storage_bucket.bucket.name
+ payload_format = var.notification_config.payload_format
+ topic = google_pubsub_topic.topic[0].id
+ custom_attributes = var.notification_config.custom_attributes
+ event_types = var.notification_config.event_types
+ object_name_prefix = var.notification_config.object_name_prefix
+ depends_on = [google_pubsub_topic_iam_binding.binding]
}
resource "google_pubsub_topic_iam_binding" "binding" {
count = local.notification ? 1 : 0
diff --git a/modules/gcs/variables.tf b/modules/gcs/variables.tf
index 2e1517234..58aec4dfb 100644
--- a/modules/gcs/variables.tf
+++ b/modules/gcs/variables.tf
@@ -17,10 +17,10 @@
variable "cors" {
description = "CORS configuration for the bucket. Defaults to null."
type = object({
- origin = list(string)
- method = list(string)
- response_header = list(string)
- max_age_seconds = number
+ origin = optional(list(string))
+ method = optional(list(string))
+ response_header = optional(list(string))
+ max_age_seconds = optional(number)
})
default = null
}
@@ -49,26 +49,53 @@ variable "labels" {
default = {}
}
-variable "lifecycle_rule" {
+variable "lifecycle_rules" {
description = "Bucket lifecycle rule."
- type = object({
+ type = map(object({
action = object({
type = string
- storage_class = string
+ storage_class = optional(string)
})
condition = object({
- age = number
- created_before = string
- with_state = string
- matches_storage_class = list(string)
- num_newer_versions = string
- custom_time_before = string
- days_since_custom_time = string
- days_since_noncurrent_time = string
- noncurrent_time_before = string
+ age = optional(number)
+ created_before = optional(string)
+ custom_time_before = optional(string)
+ days_since_custom_time = optional(number)
+ days_since_noncurrent_time = optional(number)
+ matches_prefix = optional(list(string))
+ matches_storage_class = optional(list(string)) # STANDARD, MULTI_REGIONAL, REGIONAL, NEARLINE, COLDLINE, ARCHIVE, DURABLE_REDUCED_AVAILABILITY
+ matches_suffix = optional(list(string))
+ noncurrent_time_before = optional(string)
+ num_newer_versions = optional(number)
+ with_state = optional(string) # "LIVE", "ARCHIVED", "ANY"
})
- })
- default = null
+ }))
+ default = {}
+ nullable = false
+ validation {
+ condition = alltrue([
+ for k, v in var.lifecycle_rules : v.action != null && v.condition != null
+ ])
+ error_message = "Lifecycle rules action and condition cannot be null."
+ }
+ validation {
+ condition = alltrue([
+ for k, v in var.lifecycle_rules : contains(
+ ["Delete", "SetStorageClass", "AbortIncompleteMultipartUpload"],
+ v.action.type
+ )
+ ])
+ error_message = "Lifecycle rules action type has unsupported value."
+ }
+ validation {
+ condition = alltrue([
+ for k, v in var.lifecycle_rules :
+ v.action.type != "SetStorageClass"
+ ||
+ v.action.storage_class != null
+ ])
+ error_message = "Lifecycle rules with action type SetStorageClass require a storage class."
+ }
}
variable "location" {
@@ -81,7 +108,7 @@ variable "logging_config" {
description = "Bucket logging configuration."
type = object({
log_bucket = string
- log_object_prefix = string
+ log_object_prefix = optional(string)
})
default = null
}
@@ -94,12 +121,13 @@ variable "name" {
variable "notification_config" {
description = "GCS Notification configuration."
type = object({
- enabled = bool
- payload_format = string
- topic_name = string
- sa_email = string
- event_types = list(string)
- custom_attributes = map(string)
+ enabled = bool
+ payload_format = string
+ topic_name = string
+ sa_email = string
+ event_types = optional(list(string))
+ custom_attributes = optional(map(string))
+ object_name_prefix = optional(string)
})
default = null
}
@@ -123,7 +151,7 @@ variable "retention_policy" {
description = "Bucket retention policy."
type = object({
retention_period = number
- is_locked = bool
+ is_locked = optional(bool)
})
default = null
}
@@ -153,8 +181,8 @@ variable "versioning" {
variable "website" {
description = "Bucket website."
type = object({
- main_page_suffix = string
- not_found_page = string
+ main_page_suffix = optional(string)
+ not_found_page = optional(string)
})
default = null
}
diff --git a/modules/gcs/versions.tf b/modules/gcs/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/gcs/versions.tf
+++ b/modules/gcs/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/gke-cluster/README.md b/modules/gke-cluster/README.md
index 55b594c69..2e09aeb11 100644
--- a/modules/gke-cluster/README.md
+++ b/modules/gke-cluster/README.md
@@ -22,7 +22,7 @@ module "cluster-1" {
master_authorized_ranges = {
internal-vms = "10.0.0.0/8"
}
- master_ipv4_cidr_block = "192.168.0.0/28"
+ master_ipv4_cidr_block = "192.168.0.0/28"
}
max_pods_per_node = 32
private_cluster_config = {
@@ -33,7 +33,7 @@ module "cluster-1" {
environment = "dev"
}
}
-# tftest modules=1 resources=1
+# tftest modules=1 resources=1 inventory=basic.yaml
```
### GKE Cluster with Dataplane V2 enabled
@@ -42,7 +42,7 @@ module "cluster-1" {
module "cluster-1" {
source = "./fabric/modules/gke-cluster"
project_id = "myproject"
- name = "cluster-1"
+ name = "cluster-dataplane-v2"
location = "europe-west1-b"
vpc_config = {
network = var.vpc.self_link
@@ -54,7 +54,7 @@ module "cluster-1" {
master_authorized_ranges = {
internal-vms = "10.0.0.0/8"
}
- master_ipv4_cidr_block = "192.168.0.0/28"
+ master_ipv4_cidr_block = "192.168.0.0/28"
}
private_cluster_config = {
enable_private_endpoint = true
@@ -68,7 +68,60 @@ module "cluster-1" {
environment = "dev"
}
}
-# tftest modules=1 resources=1
+# tftest modules=1 resources=1 inventory=dataplane-v2.yaml
+```
+### Autopilot Cluster
+
+```hcl
+module "cluster-autopilot" {
+ source = "./fabric/modules/gke-cluster"
+ project_id = "myproject"
+ name = "cluster-autopilot"
+ location = "europe-west1-b"
+ vpc_config = {
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ secondary_range_names = {
+ pods = "pods"
+ services = "services"
+ }
+ master_authorized_ranges = {
+ internal-vms = "10.0.0.0/8"
+ }
+ master_ipv4_cidr_block = "192.168.0.0/28"
+ }
+ enable_features = {
+ autopilot = true
+ workload_identity = false
+ }
+}
+# tftest modules=1 resources=1 inventory=autopilot.yaml
+```
+
+### Cloud DNS
+
+This example shows how to [use Cloud DNS as a Kubernetes DNS provider](https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-dns) for GKE Standard clusters.
+
+```hcl
+module "cluster-1" {
+ source = "./fabric/modules/gke-cluster"
+ project_id = var.project_id
+ name = "cluster-1"
+ location = "europe-west1-b"
+ vpc_config = {
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ secondary_range_names = { pods = "pods", services = "services" }
+ }
+ enable_features = {
+ dns = {
+ provider = "CLOUD_DNS"
+ scope = "CLUSTER_SCOPE"
+ domain = "gke.local"
+ }
+ }
+}
+# tftest modules=1 resources=1 inventory=dns.yaml
```
@@ -76,24 +129,25 @@ module "cluster-1" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [location](variables.tf#L117) | Cluster zone or region. | string | ✓ | |
-| [name](variables.tf#L174) | Cluster name. | string | ✓ | |
-| [project_id](variables.tf#L200) | Cluster project id. | string | ✓ | |
-| [vpc_config](variables.tf#L211) | VPC-level configuration. | object({…}) | ✓ | |
+| [location](variables.tf#L119) | Cluster zone or region. | string | ✓ | |
+| [name](variables.tf#L176) | Cluster name. | string | ✓ | |
+| [project_id](variables.tf#L202) | Cluster project id. | string | ✓ | |
+| [vpc_config](variables.tf#L219) | VPC-level configuration. | object({…}) | ✓ | |
| [cluster_autoscaling](variables.tf#L17) | Enable and configure limits for Node Auto-Provisioning with Cluster Autoscaler. | object({…}) | | null |
| [description](variables.tf#L38) | Cluster description. | string | | null |
| [enable_addons](variables.tf#L44) | Addons enabled in the cluster (true means enabled). | object({…}) | | {…} |
-| [enable_features](variables.tf#L68) | Enable cluster-level features. Certain features allow configuration. | object({…}) | | {…} |
-| [issue_client_certificate](variables.tf#L105) | Enable issuing client certificate. | bool | | false |
-| [labels](variables.tf#L111) | Cluster resource labels. | map(string) | | null |
-| [logging_config](variables.tf#L122) | Logging configuration. | list(string) | | ["SYSTEM_COMPONENTS"] |
-| [maintenance_config](variables.tf#L128) | Maintenance window configuration. | object({…}) | | {…} |
-| [max_pods_per_node](variables.tf#L151) | Maximum number of pods per node in this cluster. | number | | 110 |
-| [min_master_version](variables.tf#L157) | Minimum version of the master, defaults to the version of the most recent official release. | string | | null |
-| [monitoring_config](variables.tf#L163) | Monitoring components. | object({…}) | | {…} |
-| [node_locations](variables.tf#L179) | Zones in which the cluster's nodes are located. | list(string) | | [] |
-| [private_cluster_config](variables.tf#L186) | Private cluster configuration. | object({…}) | | null |
-| [release_channel](variables.tf#L205) | Release channel for GKE upgrades. | string | | null |
+| [enable_features](variables.tf#L68) | Enable cluster-level features. Certain features allow configuration. | object({…}) | | {…} |
+| [issue_client_certificate](variables.tf#L107) | Enable issuing client certificate. | bool | | false |
+| [labels](variables.tf#L113) | Cluster resource labels. | map(string) | | null |
+| [logging_config](variables.tf#L124) | Logging configuration. | list(string) | | ["SYSTEM_COMPONENTS"] |
+| [maintenance_config](variables.tf#L130) | Maintenance window configuration. | object({…}) | | {…} |
+| [max_pods_per_node](variables.tf#L153) | Maximum number of pods per node in this cluster. | number | | 110 |
+| [min_master_version](variables.tf#L159) | Minimum version of the master, defaults to the version of the most recent official release. | string | | null |
+| [monitoring_config](variables.tf#L165) | Monitoring components. | object({…}) | | {…} |
+| [node_locations](variables.tf#L181) | Zones in which the cluster's nodes are located. | list(string) | | [] |
+| [private_cluster_config](variables.tf#L188) | Private cluster configuration. | object({…}) | | null |
+| [release_channel](variables.tf#L207) | Release channel for GKE upgrades. | string | | null |
+| [tags](variables.tf#L213) | Network tags applied to nodes. | list(string) | | null |
## Outputs
diff --git a/modules/gke-cluster/main.tf b/modules/gke-cluster/main.tf
index f4b86bf66..0079dd8d8 100644
--- a/modules/gke-cluster/main.tf
+++ b/modules/gke-cluster/main.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,12 @@
*/
resource "google_container_cluster" "cluster" {
+ lifecycle {
+ ignore_changes = [
+ node_config[0].boot_disk_kms_key,
+ node_config[0].spot
+ ]
+ }
provider = google-beta
project = var.project_id
name = var.name
@@ -49,13 +55,17 @@ resource "google_container_cluster" "cluster" {
# the default nodepool is deleted here, use the gke-nodepool module instead
# default nodepool configuration based on a shielded_nodes variable
- node_config {
- dynamic "shielded_instance_config" {
- for_each = var.enable_features.shielded_nodes ? [""] : []
- content {
- enable_secure_boot = true
- enable_integrity_monitoring = true
+ dynamic "node_config" {
+ for_each = var.enable_features.autopilot ? [] : [""]
+ content {
+ dynamic "shielded_instance_config" {
+ for_each = var.enable_features.shielded_nodes ? [""] : []
+ content {
+ enable_secure_boot = true
+ enable_integrity_monitoring = true
+ }
}
+ tags = var.tags
}
}
@@ -130,7 +140,17 @@ resource "google_container_cluster" "cluster" {
dynamic "cluster_autoscaling" {
for_each = var.cluster_autoscaling == null ? [] : [""]
content {
- enabled = true
+ enabled = var.enable_features.autopilot ? null : true
+
+ dynamic "auto_provisioning_defaults" {
+ for_each = var.cluster_autoscaling.auto_provisioning_defaults != null ? [""] : []
+ content {
+ boot_disk_kms_key = var.cluster_autoscaling.auto_provisioning_defaults.boot_disk_kms_key
+ image_type = var.cluster_autoscaling.auto_provisioning_defaults.image_type
+ oauth_scopes = var.cluster_autoscaling.auto_provisioning_defaults.oauth_scopes
+ service_account = var.cluster_autoscaling.auto_provisioning_defaults.service_account
+ }
+ }
dynamic "resource_limits" {
for_each = var.cluster_autoscaling.cpu_limits != null ? [""] : []
content {
@@ -160,11 +180,11 @@ resource "google_container_cluster" "cluster" {
}
dynamic "dns_config" {
- for_each = var.enable_features.cloud_dns != null ? [""] : []
+ for_each = var.enable_features.dns != null ? [""] : []
content {
- cluster_dns = enable_features.cloud_dns.cluster_dns
- cluster_dns_scope = enable_features.cloud_dns.cluster_dns_scope
- cluster_dns_domain = enable_features.cloud_dns.cluster_dns_domain
+ cluster_dns = var.enable_features.dns.provider
+ cluster_dns_scope = var.enable_features.dns.scope
+ cluster_dns_domain = var.enable_features.dns.domain
}
}
@@ -190,6 +210,13 @@ resource "google_container_cluster" "cluster" {
}
}
+ dynamic "gateway_api_config" {
+ for_each = var.enable_features.gateway_api ? [""] : []
+ content {
+ channel = "CHANNEL_STANDARD"
+ }
+ }
+
maintenance_policy {
dynamic "daily_maintenance_window" {
for_each = (
@@ -248,6 +275,13 @@ resource "google_container_cluster" "cluster" {
}
}
+ dynamic "mesh_certificates" {
+ for_each = var.enable_features.mesh_certificates != null ? [""] : []
+ content {
+ enable_certificates = var.enable_features.mesh_certificates
+ }
+ }
+
dynamic "monitoring_config" {
for_each = var.monitoring_config != null && !var.enable_features.autopilot ? [""] : []
content {
diff --git a/modules/gke-cluster/variables.tf b/modules/gke-cluster/variables.tf
index f9a3b69e3..a51ff2087 100644
--- a/modules/gke-cluster/variables.tf
+++ b/modules/gke-cluster/variables.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -70,7 +70,7 @@ variable "enable_features" {
type = object({
autopilot = optional(bool, false)
binary_authorization = optional(bool, false)
- cloud_dns = optional(object({
+ dns = optional(object({
provider = optional(string)
scope = optional(string)
domain = optional(string)
@@ -80,9 +80,11 @@ variable "enable_features" {
key_name = string
}))
dataplane_v2 = optional(bool, false)
+ gateway_api = optional(bool, false)
groups_for_rbac = optional(string)
intranode_visibility = optional(bool, false)
l4_ilb_subsetting = optional(bool, false)
+ mesh_certificates = optional(bool)
pod_security_policy = optional(bool, false)
resource_usage_export = optional(object({
dataset = string
@@ -95,7 +97,7 @@ variable "enable_features" {
topic_id = optional(string)
}))
vertical_pod_autoscaling = optional(bool, false)
- workload_identity = optional(bool, false)
+ workload_identity = optional(bool, true)
})
default = {
workload_identity = true
@@ -208,6 +210,12 @@ variable "release_channel" {
default = null
}
+variable "tags" {
+ description = "Network tags applied to nodes."
+ type = list(string)
+ default = null
+}
+
variable "vpc_config" {
description = "VPC-level configuration."
type = object({
diff --git a/modules/gke-cluster/versions.tf b/modules/gke-cluster/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/gke-cluster/versions.tf
+++ b/modules/gke-cluster/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/gke-hub/README.md b/modules/gke-hub/README.md
index 9fe47344d..793a81cac 100644
--- a/modules/gke-hub/README.md
+++ b/modules/gke-hub/README.md
@@ -56,7 +56,7 @@ module "cluster_1" {
master_authorized_ranges = {
fc1918_10_8 = "10.0.0.0/8"
}
- master_ipv4_cidr_block = "192.168.0.0/28"
+ master_ipv4_cidr_block = "192.168.0.0/28"
}
enable_features = {
dataplane_v2 = true
@@ -115,11 +115,11 @@ module "hub" {
}
}
configmanagement_clusters = {
- "default" = [ "cluster-1" ]
+ "default" = ["cluster-1"]
}
}
-# tftest modules=4 resources=15
+# tftest modules=4 resources=16
```
## Multi-cluster mesh on GKE
@@ -141,6 +141,13 @@ module "project" {
]
}
+resource "google_project_iam_member" "gkehub_fix" {
+ member = "serviceAccount:${module.project.service_accounts.robots.fleet}"
+ project = module.project.project_id
+ role = "roles/gkehub.serviceAgent"
+}
+
+
module "vpc" {
source = "./fabric/modules/net-vpc"
project_id = module.project.project_id
@@ -151,7 +158,7 @@ module "vpc" {
ip_cidr_range = "10.0.1.0/24"
name = "subnet-cluster-1"
region = "europe-west1"
- secondary_ip_range = {
+ secondary_ip_ranges = {
pods = "10.1.0.0/16"
services = "10.2.0.0/24"
}
@@ -160,16 +167,16 @@ module "vpc" {
ip_cidr_range = "10.0.2.0/24"
name = "subnet-cluster-2"
region = "europe-west4"
- secondary_ip_range = {
+ secondary_ip_ranges = {
pods = "10.3.0.0/16"
services = "10.4.0.0/24"
}
},
{
- ip_cidr_range = "10.0.0.0/28"
- name = "subnet-mgmt"
- region = "europe-west1"
- secondary_ip_range = null
+ ip_cidr_range = "10.0.0.0/28"
+ name = "subnet-mgmt"
+ region = "europe-west1"
+ secondary_ip_ranges = null
}
]
}
@@ -216,34 +223,40 @@ module "cluster_1" {
mgmt = "10.0.0.0/28"
pods-cluster-1 = "10.3.0.0/16"
}
- master_ipv4_cidr_block = "192.168.1.0/28"
+ master_ipv4_cidr_block = "192.168.1.0/28"
}
private_cluster_config = {
enable_private_endpoint = false
master_global_access = true
}
+
release_channel = "REGULAR"
labels = {
mesh_id = "proj-${module.project.number}"
}
+ enable_features = {
+ workload_identity = true
+ dataplane_v2 = true
+ }
}
module "cluster_1_nodepool" {
source = "./fabric/modules/gke-nodepool"
project_id = module.project.project_id
cluster_name = module.cluster_1.name
+ cluster_id = module.cluster_1.id
location = "europe-west1"
- name = "nodepool"
+ name = "cluster-1-nodepool"
node_count = { initial = 1 }
service_account = { create = true }
tags = ["cluster-1-node"]
}
module "cluster_2" {
- source = "./fabric/modules/gke-cluster"
- project_id = module.project.project_id
- name = "cluster-2"
- location = "europe-west4"
+ source = "./fabric/modules/gke-cluster"
+ project_id = module.project.project_id
+ name = "cluster-2"
+ location = "europe-west4"
vpc_config = {
network = module.vpc.self_link
subnetwork = module.vpc.subnet_self_links["europe-west4/subnet-cluster-2"]
@@ -251,7 +264,7 @@ module "cluster_2" {
mgmt = "10.0.0.0/28"
pods-cluster-1 = "10.3.0.0/16"
}
- master_ipv4_cidr_block = "192.168.2.0/28"
+ master_ipv4_cidr_block = "192.168.2.0/28"
}
private_cluster_config = {
enable_private_endpoint = false
@@ -261,14 +274,19 @@ module "cluster_2" {
labels = {
mesh_id = "proj-${module.project.number}"
}
+ enable_features = {
+ workload_identity = true
+ dataplane_v2 = true
+ }
}
module "cluster_2_nodepool" {
- source = "./fabric/modules/gke-nodepool"
- project_id = module.project.project_id
- cluster_name = module.cluster_2.name
- location = "europe-west4"
- name = "nodepool"
+ source = "./fabric/modules/gke-nodepool"
+ project_id = module.project.project_id
+ cluster_name = module.cluster_2.name
+ cluster_id = module.cluster_2.id
+ location = "europe-west4"
+ name = "cluster-2-nodepool"
node_count = { initial = 1 }
service_account = { create = true }
tags = ["cluster-2-node"]
@@ -277,7 +295,8 @@ module "cluster_2_nodepool" {
module "hub" {
source = "./fabric/modules/gke-hub"
project_id = module.project.project_id
- clusters = {
+ depends_on = [google_project_iam_member.gkehub_fix]
+ clusters = {
cluster-1 = module.cluster_1.id
cluster-2 = module.cluster_2.id
}
@@ -295,7 +314,7 @@ module "hub" {
]
}
-# tftest modules=8 resources=28
+# tftest modules=8 resources=31
```
@@ -307,7 +326,7 @@ module "hub" {
| [clusters](variables.tf#L17) | Clusters members of this GKE Hub in name => id format. | map(string) | | {} |
| [configmanagement_clusters](variables.tf#L24) | Config management features enabled on specific sets of member clusters, in config name => [cluster name] format. | map(list(string)) | | {} |
| [configmanagement_templates](variables.tf#L31) | Sets of config management configurations that can be applied to member clusters, in config name => {options} format. | map(object({…})) | | {} |
-| [features](variables.tf#L66) | Enable and configue fleet features. | object({…}) | | {…} |
+| [features](variables.tf#L66) | Enable and configue fleet features. | object({…}) | | {…} |
| [workload_identity_clusters](variables.tf#L92) | Clusters that will use Fleet Workload Identity. | list(string) | | [] |
## Outputs
diff --git a/modules/gke-hub/main.tf b/modules/gke-hub/main.tf
index f433d3227..ddd35a462 100644
--- a/modules/gke-hub/main.tf
+++ b/modules/gke-hub/main.tf
@@ -70,6 +70,20 @@ resource "google_gke_hub_feature" "default" {
}
}
+resource "google_gke_hub_feature_membership" "servicemesh" {
+ provider = google-beta
+ for_each = var.features.servicemesh ? var.clusters : {}
+ project = var.project_id
+ location = "global"
+ feature = google_gke_hub_feature.default["servicemesh"].name
+ membership = google_gke_hub_membership.default[each.key].membership_id
+
+ mesh {
+ management = "MANAGEMENT_AUTOMATIC"
+ control_plane = "AUTOMATIC"
+ }
+}
+
resource "google_gke_hub_feature_membership" "default" {
provider = google-beta
for_each = local.cluster_cm_config
diff --git a/modules/gke-hub/variables.tf b/modules/gke-hub/variables.tf
index c7133c07f..25e3d21d5 100644
--- a/modules/gke-hub/variables.tf
+++ b/modules/gke-hub/variables.tf
@@ -66,12 +66,12 @@ variable "configmanagement_templates" {
variable "features" {
description = "Enable and configue fleet features."
type = object({
- appdevexperience = bool
- configmanagement = bool
- identityservice = bool
- multiclusteringress = string
- multiclusterservicediscovery = bool
- servicemesh = bool
+ appdevexperience = optional(bool, false)
+ configmanagement = optional(bool, false)
+ identityservice = optional(bool, false)
+ multiclusteringress = optional(string, null)
+ multiclusterservicediscovery = optional(bool, false)
+ servicemesh = optional(bool, false)
})
default = {
appdevexperience = false
diff --git a/modules/gke-hub/versions.tf b/modules/gke-hub/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/gke-hub/versions.tf
+++ b/modules/gke-hub/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/gke-nodepool/README.md b/modules/gke-nodepool/README.md
index 4c471c606..2f632c9c7 100644
--- a/modules/gke-nodepool/README.md
+++ b/modules/gke-nodepool/README.md
@@ -10,13 +10,13 @@ If no specific node configuration is set via variables, the module uses the prov
```hcl
module "cluster-1-nodepool-1" {
- source = "./fabric/modules/gke-nodepool"
- project_id = "myproject"
- cluster_name = "cluster-1"
- location = "europe-west1-b"
- name = "nodepool-1"
+ source = "./fabric/modules/gke-nodepool"
+ project_id = "myproject"
+ cluster_name = "cluster-1"
+ location = "europe-west1-b"
+ name = "nodepool-1"
}
-# tftest modules=1 resources=1
+# tftest modules=1 resources=1 inventory=basic.yaml
```
### Internally managed service account
@@ -27,35 +27,25 @@ If you create a new service account, its resource and email (in both plain and I
#### GCE default service account
-To use the GCE default service account, you can ignore the variable which is equivalent to `{ create = null, email = null }`.
-
-```hcl
-module "cluster-1-nodepool-1" {
- source = "./fabric/modules/gke-nodepool"
- project_id = "myproject"
- cluster_name = "cluster-1"
- location = "europe-west1-b"
- name = "nodepool-1"
-}
-# tftest modules=1 resources=1
-```
+To use the GCE default service account, you can ignore the variable which is equivalent to `{ create = null, email = null }`. This is what the first example of this document does.
#### Externally defined service account
-To use an existing service account, pass in just the `email` attribute.
+To use an existing service account, pass in just the `email` attribute. If you do this, will most likely want to use the `cloud-platform` scope.
```hcl
module "cluster-1-nodepool-1" {
- source = "./fabric/modules/gke-nodepool"
- project_id = "myproject"
- cluster_name = "cluster-1"
- location = "europe-west1-b"
- name = "nodepool-1"
+ source = "./fabric/modules/gke-nodepool"
+ project_id = "myproject"
+ cluster_name = "cluster-1"
+ location = "europe-west1-b"
+ name = "nodepool-1"
service_account = {
- email = "foo-bar@myproject.iam.gserviceaccount.com"
+ email = "foo-bar@myproject.iam.gserviceaccount.com"
+ oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
}
}
-# tftest modules=1 resources=1
+# tftest modules=1 resources=1 inventory=external-sa.yaml
```
#### Auto-created service account
@@ -64,18 +54,54 @@ To have the module create a service account, set the `create` attribute to `true
```hcl
module "cluster-1-nodepool-1" {
- source = "./fabric/modules/gke-nodepool"
- project_id = "myproject"
- cluster_name = "cluster-1"
- location = "europe-west1-b"
- name = "nodepool-1"
+ source = "./fabric/modules/gke-nodepool"
+ project_id = "myproject"
+ cluster_name = "cluster-1"
+ location = "europe-west1-b"
+ name = "nodepool-1"
service_account = {
- create = true
- # optional
- email = "spam-eggs"
+ create = true
+ email = "spam-eggs" # optional
+ oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
}
}
-# tftest modules=1 resources=2
+# tftest modules=1 resources=2 inventory=create-sa.yaml
+```
+### Node & node pool configuration
+
+```hcl
+module "cluster-1-nodepool-1" {
+ source = "./fabric/modules/gke-nodepool"
+ project_id = "myproject"
+ cluster_name = "cluster-1"
+ location = "europe-west1-b"
+ name = "nodepool-1"
+ labels = { environment = "dev" }
+ service_account = {
+ create = true
+ email = "nodepool-1" # optional
+ oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
+ }
+ node_config = {
+ machine_type = "n2-standard-2"
+ disk_size_gb = 50
+ disk_type = "pd-ssd"
+ ephemeral_ssd_count = 1
+ gvnic = true
+ spot = true
+ }
+ nodepool_config = {
+ autoscaling = {
+ max_node_count = 10
+ min_node_count = 1
+ }
+ management = {
+ auto_repair = true
+ auto_upgrade = false
+ }
+ }
+}
+# tftest modules=1 resources=2 inventory=config.yaml
```
@@ -83,23 +109,24 @@ module "cluster-1-nodepool-1" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [cluster_name](variables.tf#L17) | Cluster name. | string | ✓ | |
-| [location](variables.tf#L35) | Cluster location. | string | ✓ | |
-| [project_id](variables.tf#L143) | Cluster project id. | string | ✓ | |
-| [gke_version](variables.tf#L22) | Kubernetes nodes version. Ignored if auto_upgrade is set in management_config. | string | | null |
-| [labels](variables.tf#L28) | Kubernetes labels applied to each node. | map(string) | | {} |
-| [max_pods_per_node](variables.tf#L40) | Maximum number of pods per node. | number | | null |
-| [name](variables.tf#L46) | Optional nodepool name. | string | | null |
-| [node_config](variables.tf#L52) | Node-level configuration. | object({…}) | | {…} |
-| [node_count](variables.tf#L91) | Number of nodes per instance group. Initial value can only be changed by recreation, current is ignored when autoscaling is used. | object({…}) | | {…} |
-| [node_locations](variables.tf#L103) | Node locations. | list(string) | | null |
-| [nodepool_config](variables.tf#L109) | Nodepool-level configuration. | object({…}) | | null |
-| [pod_range](variables.tf#L131) | Pod secondary range configuration. | object({…}) | | null |
-| [reservation_affinity](variables.tf#L148) | Configuration of the desired reservation which instances could take capacity from. | object({…}) | | null |
-| [service_account](variables.tf#L158) | Nodepool service account. If this variable is set to null, the default GCE service account will be used. If set and email is null, a service account will be created. If scopes are null a default will be used. | object({…}) | | {} |
-| [sole_tenant_nodegroup](variables.tf#L169) | Sole tenant node group. | string | | null |
-| [tags](variables.tf#L175) | Network tags applied to nodes. | list(string) | | null |
-| [taints](variables.tf#L181) | Kubernetes taints applied to all nodes. | list(object({…})) | | null |
+| [cluster_name](variables.tf#L23) | Cluster name. | string | ✓ | |
+| [location](variables.tf#L41) | Cluster location. | string | ✓ | |
+| [project_id](variables.tf#L149) | Cluster project id. | string | ✓ | |
+| [cluster_id](variables.tf#L17) | Cluster id. Optional, but providing cluster_id is recommended to prevent cluster misconfiguration in some of the edge cases. | string | | null |
+| [gke_version](variables.tf#L28) | Kubernetes nodes version. Ignored if auto_upgrade is set in management_config. | string | | null |
+| [labels](variables.tf#L34) | Kubernetes labels applied to each node. | map(string) | | {} |
+| [max_pods_per_node](variables.tf#L46) | Maximum number of pods per node. | number | | null |
+| [name](variables.tf#L52) | Optional nodepool name. | string | | null |
+| [node_config](variables.tf#L58) | Node-level configuration. | object({…}) | | {…} |
+| [node_count](variables.tf#L97) | Number of nodes per instance group. Initial value can only be changed by recreation, current is ignored when autoscaling is used. | object({…}) | | {…} |
+| [node_locations](variables.tf#L109) | Node locations. | list(string) | | null |
+| [nodepool_config](variables.tf#L115) | Nodepool-level configuration. | object({…}) | | null |
+| [pod_range](variables.tf#L137) | Pod secondary range configuration. | object({…}) | | null |
+| [reservation_affinity](variables.tf#L154) | Configuration of the desired reservation which instances could take capacity from. | object({…}) | | null |
+| [service_account](variables.tf#L164) | Nodepool service account. If this variable is set to null, the default GCE service account will be used. If set and email is null, a service account will be created. If scopes are null a default will be used. | object({…}) | | {} |
+| [sole_tenant_nodegroup](variables.tf#L175) | Sole tenant node group. | string | | null |
+| [tags](variables.tf#L181) | Network tags applied to nodes. | list(string) | | null |
+| [taints](variables.tf#L187) | Kubernetes taints applied to all nodes. | list(object({…})) | | null |
## Outputs
diff --git a/modules/gke-nodepool/main.tf b/modules/gke-nodepool/main.tf
index 0c35c8d0f..9ae4cf284 100644
--- a/modules/gke-nodepool/main.tf
+++ b/modules/gke-nodepool/main.tf
@@ -70,7 +70,7 @@ resource "google_service_account" "service_account" {
resource "google_container_node_pool" "nodepool" {
provider = google-beta
project = var.project_id
- cluster = var.cluster_name
+ cluster = coalesce(var.cluster_id, var.cluster_name)
location = var.location
name = var.name
version = var.gke_version
@@ -115,9 +115,9 @@ resource "google_container_node_pool" "nodepool" {
dynamic "network_config" {
for_each = var.pod_range != null ? [""] : []
content {
- create_pod_range = var.pod_range.create
- pod_ipv4_cidr_block = var.pod_range.cidr
- pod_range = var.pod_range.name
+ create_pod_range = var.pod_range.secondary_pod_range.create
+ pod_ipv4_cidr_block = var.pod_range.secondary_pod_range.cidr
+ pod_range = var.pod_range.secondary_pod_range.name
}
}
diff --git a/modules/gke-nodepool/variables.tf b/modules/gke-nodepool/variables.tf
index 15c8a1515..1166c34f4 100644
--- a/modules/gke-nodepool/variables.tf
+++ b/modules/gke-nodepool/variables.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,12 @@
* limitations under the License.
*/
+variable "cluster_id" {
+ description = "Cluster id. Optional, but providing cluster_id is recommended to prevent cluster misconfiguration in some of the edge cases."
+ type = string
+ default = null
+}
+
variable "cluster_name" {
description = "Cluster name."
type = string
@@ -159,8 +165,8 @@ variable "service_account" {
description = "Nodepool service account. If this variable is set to null, the default GCE service account will be used. If set and email is null, a service account will be created. If scopes are null a default will be used."
type = object({
create = optional(bool, false)
- email = optional(string, null)
- oauth_scopes = optional(list(string), null)
+ email = optional(string)
+ oauth_scopes = optional(list(string))
})
default = {}
nullable = false
diff --git a/modules/gke-nodepool/versions.tf b/modules/gke-nodepool/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/gke-nodepool/versions.tf
+++ b/modules/gke-nodepool/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/iam-service-account/README.md b/modules/iam-service-account/README.md
index 2c6faee52..a62ce8f53 100644
--- a/modules/iam-service-account/README.md
+++ b/modules/iam-service-account/README.md
@@ -8,12 +8,11 @@ Note that this module does not fully comply with our design principles, as outpu
```hcl
module "myproject-default-service-accounts" {
- source = "./fabric/modules/iam-service-account"
- project_id = "myproject"
- name = "vm-default"
- generate_key = true
+ source = "./fabric/modules/iam-service-account"
+ project_id = "myproject"
+ name = "vm-default"
# authoritative roles granted *on* the service accounts to other identities
- iam = {
+ iam = {
"roles/iam.serviceAccountUser" = ["user:foo@example.com"]
}
# non-authoritative roles granted *to* the service accounts on other resources
@@ -24,7 +23,7 @@ module "myproject-default-service-accounts" {
]
}
}
-# tftest modules=1 resources=5
+# tftest modules=1 resources=4 inventory=basic.yaml
```
diff --git a/modules/iam-service-account/versions.tf b/modules/iam-service-account/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/iam-service-account/versions.tf
+++ b/modules/iam-service-account/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/kms/README.md b/modules/kms/README.md
index 4fe748578..3398a595d 100644
--- a/modules/kms/README.md
+++ b/modules/kms/README.md
@@ -14,9 +14,9 @@ In this module **no lifecycle blocks are set on resources to prevent destroy**,
```hcl
module "kms" {
- source = "./fabric/modules/kms"
- project_id = "my-project"
- iam = {
+ source = "./fabric/modules/kms"
+ project_id = "my-project"
+ iam = {
"roles/cloudkms.admin" = ["user:user1@example.com"]
}
keyring = { location = "europe-west1", name = "test" }
@@ -63,8 +63,8 @@ module "kms" {
```hcl
module "kms" {
- source = "./fabric/modules/kms"
- project_id = "my-project"
+ source = "./fabric/modules/kms"
+ project_id = "my-project"
key_purpose = {
key-c = {
purpose = "ASYMMETRIC_SIGN"
@@ -74,8 +74,8 @@ module "kms" {
}
}
}
- keyring = { location = "europe-west1", name = "test" }
- keys = { key-a = null, key-b = null, key-c = null }
+ keyring = { location = "europe-west1", name = "test" }
+ keys = { key-a = null, key-b = null, key-c = null }
}
# tftest modules=1 resources=4
```
diff --git a/modules/kms/versions.tf b/modules/kms/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/kms/versions.tf
+++ b/modules/kms/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/logging-bucket/versions.tf b/modules/logging-bucket/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/logging-bucket/versions.tf
+++ b/modules/logging-bucket/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/net-address/README.md b/modules/net-address/README.md
index e51383489..23e947cd4 100644
--- a/modules/net-address/README.md
+++ b/modules/net-address/README.md
@@ -27,12 +27,12 @@ module "addresses" {
project_id = var.project_id
internal_addresses = {
ilb-1 = {
- purpose = "SHARED_LOADBALANCER_VIP"
+ purpose = "SHARED_LOADBALANCER_VIP"
region = var.region
subnetwork = var.subnet.self_link
}
ilb-2 = {
- address = "10.0.0.2"
+ address = "10.0.0.2"
region = var.region
subnetwork = var.subnet.self_link
}
@@ -66,11 +66,11 @@ module "addresses" {
project_id = var.project_id
psc_addresses = {
one = {
- address = null
+ address = null
network = var.vpc.self_link
}
two = {
- address = "10.0.0.32"
+ address = "10.0.0.32"
network = var.vpc.self_link
}
}
diff --git a/modules/net-address/versions.tf b/modules/net-address/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/net-address/versions.tf
+++ b/modules/net-address/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/net-cloudnat/versions.tf b/modules/net-cloudnat/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/net-cloudnat/versions.tf
+++ b/modules/net-cloudnat/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/net-glb/README.md b/modules/net-glb/README.md
index 4fc97b076..8cd3f353f 100644
--- a/modules/net-glb/README.md
+++ b/modules/net-glb/README.md
@@ -117,7 +117,7 @@ The module uses a classic Global Load Balancer by default. To use the non-classi
```hcl
module "glb-0" {
- source = "./fabric/modules/net-glb"
+ source = "./fabric/modules/net-glb"
project_id = "myprj"
name = "glb-test-0"
use_classic_version = false
@@ -214,6 +214,66 @@ module "glb-0" {
}
# tftest modules=1 resources=6
```
+#### Managed Instance Groups
+
+This example shows how to use the module with a manage instance group as backend:
+
+```hcl
+module "win-template" {
+ source = "./fabric/modules/compute-vm"
+ project_id = "myprj"
+ zone = "europe-west8-a"
+ name = "win-template"
+ instance_type = "n2d-standard-2"
+ create_template = true
+ boot_disk = {
+ image = "projects/windows-cloud/global/images/windows-server-2019-dc-v20221214"
+ type = "pd-balanced"
+ size = 70
+ }
+ network_interfaces = [{
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ nat = false
+ addresses = null
+ }]
+}
+
+module "win-mig" {
+ source = "./fabric/modules/compute-mig"
+ project_id = "myprj"
+ location = "europe-west8-a"
+ name = "win-mig"
+ instance_template = module.win-template.template.self_link
+ autoscaler_config = {
+ max_replicas = 3
+ min_replicas = 1
+ cooldown_period = 30
+ scaling_signals = {
+ cpu_utilization = {
+ target = 0.80
+ }
+ }
+ }
+ named_ports = {
+ http = 80
+ }
+}
+
+module "glb-0" {
+ source = "./fabric/modules/net-glb"
+ project_id = "myprj"
+ name = "glb-test-0"
+ backend_service_configs = {
+ default = {
+ backends = [
+ { backend = module.win-mig.group_manager.instance_group }
+ ]
+ }
+ }
+}
+# tftest modules=3 resources=8
+```
#### Storage Buckets
@@ -285,11 +345,13 @@ module "glb-0" {
network = "projects/myprj-host/global/networks/svpc"
subnetwork = "projects/myprj-host/regions/europe-west8/subnetworks/gce"
zone = "europe-west8-b"
- endpoints = [{
- instance = "myinstance-b-0"
- ip_address = "10.24.32.25"
- port = 80
- }]
+ endpoints = {
+ e-0 = {
+ instance = "myinstance-b-0"
+ ip_address = "10.24.32.25"
+ port = 80
+ }
+ }
}
}
}
@@ -320,12 +382,14 @@ module "glb-0" {
neg_configs = {
neg-0 = {
hybrid = {
- network = "projects/myprj-host/global/networks/svpc"
- zone = "europe-west8-b"
- endpoints = [{
- ip_address = "10.0.0.10"
- port = 80
- }]
+ network = "projects/myprj-host/global/networks/svpc"
+ zone = "europe-west8-b"
+ endpoints = {
+ e-0 = {
+ ip_address = "10.0.0.10"
+ port = 80
+ }
+ }
}
}
}
@@ -355,11 +419,13 @@ module "glb-0" {
neg_configs = {
neg-0 = {
internet = {
- use_fqdn = true
- endpoints = [{
- destination = "www.example.org"
- port = 80
- }]
+ use_fqdn = true
+ endpoints = {
+ e-0 = {
+ destination = "www.example.org"
+ port = 80
+ }
+ }
}
}
}
@@ -373,7 +439,7 @@ The module supports managing PSC NEGs if the non-classic version of the load bal
```hcl
module "glb-0" {
- source = "./fabric/modules/net-glb"
+ source = "./fabric/modules/net-glb"
project_id = "myprj"
name = "glb-test-0"
use_classic_version = false
@@ -390,7 +456,7 @@ module "glb-0" {
neg_configs = {
neg-0 = {
psc = {
- region = "europe-west8"
+ region = "europe-west8"
target_service = "europe-west8-cloudkms.googleapis.com"
}
}
@@ -432,6 +498,46 @@ module "glb-0" {
# tftest modules=1 resources=5
```
+Serverless NEGs don't use the port name but it should be set to `http`. An HTTPS frontend requires the protocol to be set to `HTTPS`, and the port name field will infer this value if omitted so you need to set it explicitly:
+
+```hcl
+module "glb-0" {
+ source = "./fabric/modules/net-glb"
+ project_id = "myprj"
+ name = "glb-test-0"
+ backend_service_configs = {
+ default = {
+ backends = [
+ { backend = "neg-0" }
+ ]
+ health_checks = []
+ port_name = "http"
+ }
+ }
+ # with a single serverless NEG the implied default health check is not needed
+ health_check_configs = {}
+ neg_configs = {
+ neg-0 = {
+ cloudrun = {
+ region = "europe-west8"
+ target_service = {
+ name = "hello"
+ }
+ }
+ }
+ }
+ protocol = "HTTPS"
+ ssl_certificates = {
+ managed_configs = {
+ default = {
+ domains = ["glb-test-0.example.org"]
+ }
+ }
+ }
+}
+# tftest modules=1 resources=6 inventory=https-sneg.yaml
+```
+
### URL Map
The module exposes the full URL map resource configuration, with some minor changes to the interface to decrease verbosity, and support for aliasing backend services via keys.
@@ -465,7 +571,7 @@ module "glb-0" {
pathmap = {
default_service = "default"
path_rules = [{
- paths = ["/other", "/other/*"]
+ paths = ["/other", "/other/*"]
service = "other"
}]
}
@@ -483,7 +589,6 @@ The module also allows managing managed and self-managed SSL certificates via th
THe [HTTPS example above](#minimal-https-examples) shows how to configure manage certificated, the following example shows how to use an unmanaged (or self managed) certificate. The example uses Terraform resource for the key and certificate so that the we don't depend on external files when running tests, in real use the key and certificate are generally provided via external files read by the Terraform `file()` function.
```hcl
-
resource "tls_private_key" "default" {
algorithm = "RSA"
rsa_bits = 4096
@@ -554,16 +659,16 @@ module "glb-0" {
neg-gce-0 = {
backends = [{
balancing_mode = "RATE"
- backend = "neg-ew8-c"
+ backend = "neg-ew8-c"
max_rate = { per_endpoint = 10 }
}]
}
neg-hybrid-0 = {
backends = [{
- backend = "neg-hello"
+ backend = "neg-hello"
}]
- health_checks = ["neg"]
- protocol = "HTTPS"
+ health_checks = ["neg"]
+ protocol = "HTTPS"
}
}
group_configs = {
@@ -600,22 +705,26 @@ module "glb-0" {
gce = {
network = "projects/myprj-host/global/networks/svpc"
subnetwork = "projects/myprj-host/regions/europe-west8/subnetworks/gce"
- zone = "europe-west8-c"
- endpoints = [{
- instance = "nginx-ew8-c"
- ip_address = "10.24.32.26"
- port = 80
- }]
+ zone = "europe-west8-c"
+ endpoints = {
+ e-0 = {
+ instance = "nginx-ew8-c"
+ ip_address = "10.24.32.26"
+ port = 80
+ }
+ }
}
}
neg-hello = {
hybrid = {
- network = "projects/myprj-host/global/networks/svpc"
- zone = "europe-west8-b"
- endpoints = [{
- ip_address = "192.168.0.3"
- port = 443
- }]
+ network = "projects/myprj-host/global/networks/svpc"
+ zone = "europe-west8-b"
+ endpoints = {
+ e-0 = {
+ ip_address = "192.168.0.3"
+ port = 443
+ }
+ }
}
}
}
@@ -691,7 +800,7 @@ module "glb-0" {
| [health_check_configs](variables-health-check.tf#L19) | Optional auto-created health check configurations, use the output self-link to set it in the auto healing policy. Refer to examples for usage. | map(object({…})) | | {…} |
| [https_proxy_config](variables.tf#L74) | HTTPS proxy connfiguration. | object({…}) | | {} |
| [labels](variables.tf#L85) | Labels set on resources. | map(string) | | {} |
-| [neg_configs](variables.tf#L96) | Optional network endpoint groups to create. Can be referenced in backends via key or outputs. | map(object({…})) | | {} |
+| [neg_configs](variables.tf#L96) | Optional network endpoint groups to create. Can be referenced in backends via key or outputs. | map(object({…})) | | {} |
| [ports](variables.tf#L187) | Optional ports for HTTP load balancer, valid ports are 80 and 8080. | list(string) | | null |
| [protocol](variables.tf#L198) | Protocol supported by this load balancer. | string | | "HTTP" |
| [ssl_certificates](variables.tf#L211) | SSL target proxy certificates (only if protocol is HTTPS) for existing, custom, and managed certificates. | object({…}) | | {} |
diff --git a/modules/net-glb/backend-service.tf b/modules/net-glb/backend-service.tf
index f8956e138..acadda3bd 100644
--- a/modules/net-glb/backend-service.tf
+++ b/modules/net-glb/backend-service.tf
@@ -60,7 +60,7 @@ resource "google_compute_backend_service" "default" {
health_checks = length(each.value.health_checks) == 0 ? null : [
for k in each.value.health_checks : lookup(local.hc_ids, k, k)
]
- load_balancing_scheme = "EXTERNAL"
+ load_balancing_scheme = var.use_classic_version ? "EXTERNAL" : "EXTERNAL_MANAGED"
port_name = (
each.value.port_name == null
? lower(each.value.protocol == null ? var.protocol : each.value.protocol)
diff --git a/modules/net-glb/negs.tf b/modules/net-glb/negs.tf
index 9edae1cd8..0011968d5 100644
--- a/modules/net-glb/negs.tf
+++ b/modules/net-glb/negs.tf
@@ -19,23 +19,23 @@
locals {
_neg_endpoints_global = flatten([
for k, v in local.neg_global : [
- for vv in v.internet.endpoints :
- merge(vv, { neg = k, use_fqdn = v.internet.use_fqdn })
+ for kk, vv in v.internet.endpoints : merge(vv, {
+ key = "${k}-${kk}", neg = k, use_fqdn = v.internet.use_fqdn
+ })
]
])
_neg_endpoints_zonal = flatten([
for k, v in local.neg_zonal : [
- for vv in v.endpoints :
- merge(vv, { neg = k, zone = v.zone })
+ for kk, vv in v.endpoints : merge(vv, {
+ key = "${k}-${kk}", neg = k, zone = v.zone
+ })
]
])
neg_endpoints_global = {
- for v in local._neg_endpoints_global :
- "${v.neg}-${v.destination}-${coalesce(v.port, "none")}" => v
+ for v in local._neg_endpoints_global : (v.key) => v
}
neg_endpoints_zonal = {
- for v in local._neg_endpoints_zonal :
- "${v.neg}-${v.ip_address}-${coalesce(v.port, "none")}" => v
+ for v in local._neg_endpoints_zonal : (v.key) => v
}
neg_global = {
for k, v in var.neg_configs :
diff --git a/modules/net-glb/variables.tf b/modules/net-glb/variables.tf
index 523b8f5f4..72e6c0c40 100644
--- a/modules/net-glb/variables.tf
+++ b/modules/net-glb/variables.tf
@@ -115,7 +115,7 @@ variable "neg_configs" {
subnetwork = string
zone = string
# default_port = optional(number)
- endpoints = optional(list(object({
+ endpoints = optional(map(object({
instance = string
ip_address = string
port = number
@@ -126,7 +126,7 @@ variable "neg_configs" {
zone = string
# re-enable once provider properly support this
# default_port = optional(number)
- endpoints = optional(list(object({
+ endpoints = optional(map(object({
ip_address = string
port = number
})))
@@ -135,7 +135,7 @@ variable "neg_configs" {
use_fqdn = optional(bool, true)
# re-enable once provider properly support this
# default_port = optional(number)
- endpoints = optional(list(object({
+ endpoints = optional(map(object({
destination = string
port = number
})))
diff --git a/modules/net-glb/versions.tf b/modules/net-glb/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/net-glb/versions.tf
+++ b/modules/net-glb/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/net-ilb-l7/README.md b/modules/net-ilb-l7/README.md
index 969a4da4f..b5862f31e 100644
--- a/modules/net-ilb-l7/README.md
+++ b/modules/net-ilb-l7/README.md
@@ -176,7 +176,7 @@ module "ilb-l7" {
backend_service_configs = {
default = {
port_name = "http"
- backends = [
+ backends = [
{ group = "default" }
]
}
@@ -228,6 +228,14 @@ module "ilb-l7" {
Similarly to instance groups, NEGs can also be managed by this module which supports GCE, hybrid, and serverless NEGs:
```hcl
+resource "google_compute_address" "test" {
+ name = "neg-test"
+ subnetwork = var.subnet.self_link
+ address_type = "INTERNAL"
+ address = "10.0.0.10"
+ region = "europe-west1"
+}
+
module "ilb-l7" {
source = "./fabric/modules/net-ilb-l7"
name = "ilb-test"
@@ -237,7 +245,7 @@ module "ilb-l7" {
default = {
backends = [{
balancing_mode = "RATE"
- group = "my-neg"
+ group = "my-neg"
max_rate = { per_endpoint = 1 }
}]
}
@@ -245,12 +253,15 @@ module "ilb-l7" {
neg_configs = {
my-neg = {
gce = {
- zone = "europe-west1-b"
- endpoints = [{
- instance = "test-1"
- ip_address = "10.0.0.10"
- port = 80
- }]
+ zone = "europe-west1-b"
+ endpoints = {
+ e-0 = {
+ instance = "test-1"
+ ip_address = google_compute_address.test.address
+ # ip_address = "10.0.0.10"
+ port = 80
+ }
+ }
}
}
}
@@ -259,7 +270,7 @@ module "ilb-l7" {
subnetwork = var.subnet.self_link
}
}
-# tftest modules=1 resources=7
+# tftest modules=1 resources=8
```
Hybrid NEGs are also supported:
@@ -274,7 +285,7 @@ module "ilb-l7" {
default = {
backends = [{
balancing_mode = "RATE"
- group = "my-neg"
+ group = "my-neg"
max_rate = { per_endpoint = 1 }
}]
}
@@ -282,11 +293,13 @@ module "ilb-l7" {
neg_configs = {
my-neg = {
hybrid = {
- zone = "europe-west1-b"
- endpoints = [{
- ip_address = "10.0.0.10"
- port = 80
- }]
+ zone = "europe-west1-b"
+ endpoints = {
+ e-0 = {
+ ip_address = "10.0.0.10"
+ port = 80
+ }
+ }
}
}
}
@@ -310,7 +323,7 @@ module "ilb-l7" {
default = {
backends = [{
balancing_mode = "RATE"
- group = "my-neg"
+ group = "my-neg"
max_rate = { per_endpoint = 1 }
}]
}
@@ -367,7 +380,7 @@ module "ilb-l7" {
pathmap = {
default_service = "default"
path_rules = [{
- paths = ["/video", "/video/*"]
+ paths = ["/video", "/video/*"]
service = "video"
}]
}
@@ -512,20 +525,24 @@ module "ilb-l7" {
neg-nginx-ew8-c = {
gce = {
zone = "europe-west8-c"
- endpoints = [{
- instance = "nginx-ew8-c"
- ip_address = "10.24.32.26"
- port = 80
- }]
+ endpoints = {
+ e-0 = {
+ instance = "nginx-ew8-c"
+ ip_address = "10.24.32.26"
+ port = 80
+ }
+ }
}
}
neg-home-hello = {
hybrid = {
- zone = "europe-west8-b"
- endpoints = [{
- ip_address = "192.168.0.3"
- port = 443
- }]
+ zone = "europe-west8-b"
+ endpoints = {
+ e-0 = {
+ ip_address = "192.168.0.3"
+ port = 443
+ }
+ }
}
}
}
@@ -597,7 +614,7 @@ module "ilb-l7" {
| [group_configs](variables.tf#L36) | Optional unmanaged groups to create. Can be referenced in backends via key or outputs. | map(object({…})) | | {} |
| [health_check_configs](variables-health-check.tf#L19) | Optional auto-created health check configurations, use the output self-link to set it in the auto healing policy. Refer to examples for usage. | map(object({…})) | | {…} |
| [labels](variables.tf#L48) | Labels set on resources. | map(string) | | {} |
-| [neg_configs](variables.tf#L59) | Optional network endpoint groups to create. Can be referenced in backends via key or outputs. | map(object({…})) | | {} |
+| [neg_configs](variables.tf#L59) | Optional network endpoint groups to create. Can be referenced in backends via key or outputs. | map(object({…})) | | {} |
| [network_tier_premium](variables.tf#L119) | Use premium network tier. Defaults to true. | bool | | true |
| [ports](variables.tf#L126) | Optional ports for HTTP load balancer, valid ports are 80 and 8080. | list(string) | | null |
| [protocol](variables.tf#L137) | Protocol supported by this load balancer. | string | | "HTTP" |
diff --git a/modules/net-ilb-l7/backend-service.tf b/modules/net-ilb-l7/backend-service.tf
index e2d92299f..a517bd08c 100644
--- a/modules/net-ilb-l7/backend-service.tf
+++ b/modules/net-ilb-l7/backend-service.tf
@@ -23,6 +23,9 @@ locals {
},
{
for k, v in google_compute_network_endpoint_group.default : k => v.id
+ },
+ {
+ for k, v in google_compute_region_network_endpoint_group.default : k => v.id
}
)
hc_ids = {
diff --git a/modules/net-ilb-l7/main.tf b/modules/net-ilb-l7/main.tf
index 5b6211a39..803b3ff5c 100644
--- a/modules/net-ilb-l7/main.tf
+++ b/modules/net-ilb-l7/main.tf
@@ -15,9 +15,12 @@
*/
locals {
+ # we need keys in the endpoint type to address issue #1055
_neg_endpoints = flatten([
for k, v in local.neg_zonal : [
- for vv in v.endpoints : merge(vv, { neg = k, zone = v.zone })
+ for kk, vv in v.endpoints : merge(vv, {
+ key = "${k}-${kk}", neg = k, zone = v.zone
+ })
]
])
fwd_rule_ports = (
@@ -29,8 +32,7 @@ locals {
: google_compute_region_target_http_proxy.default.0.id
)
neg_endpoints = {
- for v in local._neg_endpoints :
- "${v.neg}-${v.ip_address}-${coalesce(v.port, "none")}" => v
+ for v in local._neg_endpoints : (v.key) => v
}
neg_regional = {
for k, v in var.neg_configs :
diff --git a/modules/net-ilb-l7/variables.tf b/modules/net-ilb-l7/variables.tf
index 0577ddf6e..09b3f7ac7 100644
--- a/modules/net-ilb-l7/variables.tf
+++ b/modules/net-ilb-l7/variables.tf
@@ -73,7 +73,7 @@ variable "neg_configs" {
# default_port = optional(number)
network = optional(string)
subnetwork = optional(string)
- endpoints = optional(list(object({
+ endpoints = optional(map(object({
instance = string
ip_address = string
port = number
@@ -85,7 +85,7 @@ variable "neg_configs" {
network = optional(string)
# re-enable once provider properly support this
# default_port = optional(number)
- endpoints = optional(list(object({
+ endpoints = optional(map(object({
ip_address = string
port = number
})))
diff --git a/modules/net-ilb-l7/versions.tf b/modules/net-ilb-l7/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/net-ilb-l7/versions.tf
+++ b/modules/net-ilb-l7/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/net-ilb/README.md b/modules/net-ilb/README.md
index bf2b507bd..48c1d9081 100644
--- a/modules/net-ilb/README.md
+++ b/modules/net-ilb/README.md
@@ -12,6 +12,62 @@ One other issue is a `Provider produced inconsistent final plan` error which is
## Examples
+### Reference existing MIGs
+
+This example shows how to reference existing Managed Infrastructure Groups (MIGs).
+
+```hcl
+module "instance_template" {
+ source = "./fabric/modules/compute-vm"
+ project_id = var.project_id
+ create_template = true
+ name = "vm-test"
+ service_account_create = true
+ zone = "europe-west1-b"
+
+ network_interfaces = [
+ {
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ }
+ ]
+
+ tags = [
+ "http-server"
+ ]
+}
+
+module "mig" {
+ source = "./fabric/modules/compute-mig"
+ project_id = var.project_id
+ location = "europe-west1"
+ name = "mig-test"
+ target_size = 1
+ instance_template = module.instance_template.template.self_link
+}
+
+module "ilb" {
+ source = "./fabric/modules/net-ilb"
+ project_id = var.project_id
+ region = "europe-west1"
+ name = "ilb-test"
+ service_label = "ilb-test"
+ vpc_config = {
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ }
+ backends = [{
+ group = module.mig.group_manager.instance_group
+ }]
+ health_check_config = {
+ http = {
+ port = 80
+ }
+ }
+}
+# tftest modules=3 resources=6
+```
+
### Externally managed instances
This examples shows how to create an ILB by combining externally managed instances (in a custom module or even outside of the current root module) in an unmanaged group. When using internally managed groups, remember to run `terraform apply` each time group instances change.
@@ -37,7 +93,7 @@ module "ilb" {
}
}
backends = [{
- group = module.ilb.groups.my-group.self_link
+ group = module.ilb.groups.my-group.self_link
}]
health_check_config = {
http = {
@@ -96,7 +152,7 @@ module "ilb" {
vpc_config = {
network = var.vpc.self_link
subnetwork = var.subnet.self_link
- }
+ }
ports = [80]
backends = [
for z, mod in module.instance-group : {
diff --git a/modules/net-ilb/versions.tf b/modules/net-ilb/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/net-ilb/versions.tf
+++ b/modules/net-ilb/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/net-interconnect-attachment-direct/versions.tf b/modules/net-interconnect-attachment-direct/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/net-interconnect-attachment-direct/versions.tf
+++ b/modules/net-interconnect-attachment-direct/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/net-vpc-firewall/README.md b/modules/net-vpc-firewall/README.md
index 58c116561..f886035ba 100644
--- a/modules/net-vpc-firewall/README.md
+++ b/modules/net-vpc-firewall/README.md
@@ -33,7 +33,7 @@ Some implicit defaults are used in the rules variable types and can be controlle
- action is controlled via the `deny` attribute which defaults to `true` for egress and `false` for ingress
- priority defaults to `1000`
-- destination ranges (for egress) and source ranges (for ingress) default to `["0.0.0.0/0"]` if not explicitly set
+- destination ranges (for egress) and source ranges (for ingress) default to `["0.0.0.0/0"]` if not explicitly set or set to `null`, to disable the behaviour set ranges to the empty list (`[]`)
- rules default to all protocols if not set
```hcl
@@ -44,32 +44,40 @@ module "firewall" {
default_rules_config = {
admin_ranges = ["10.0.0.0/8"]
}
- egress_rules = {
- # implicit `deny` action
+ egress_rules = {
+ # implicit deny action
allow-egress-rfc1918 = {
+ deny = false
description = "Allow egress to RFC 1918 ranges."
- destination_ranges = [
+ destination_ranges = [
"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"
]
- # implicit { protocol = "all" } rule
+ }
+ allow-egress-tag = {
+ deny = false
+ description = "Allow egress from a specific tag to 0/0."
+ targets = ["target-tag"]
}
deny-egress-all = {
description = "Block egress."
- # implicit ["0.0.0.0/0"] destination ranges
- # implicit { protocol = "all" } rule
}
}
ingress_rules = {
- # implicit `allow` action
+ # implicit allow action
allow-ingress-ntp = {
- description = "Allow NTP service based on tag."
- source_ranges = ["0.0.0.0/0"]
- targets = ["ntp-svc"]
- rules = [{ protocol = "udp", ports = [123] }]
+ description = "Allow NTP service based on tag."
+ targets = ["ntp-svc"]
+ rules = [{ protocol = "udp", ports = [123] }]
+ }
+ allow-ingress-tag = {
+ description = "Allow ingress from a specific tag."
+ source_ranges = []
+ sources = ["client-tag"]
+ targets = ["target-tag"]
}
}
}
-# tftest modules=1 resources=7
+# tftest modules=1 resources=9
```
### Controlling or turning off default rules
@@ -108,7 +116,7 @@ module "firewall" {
project_id = "my-project"
network = "my-network"
default_rules_config = {
- ssh_ranges = []
+ ssh_ranges = []
}
}
# tftest modules=1 resources=2
@@ -134,34 +142,54 @@ The module includes a rules factory (see [Resource Factories](../../blueprints/f
```hcl
module "firewall" {
- source = "./fabric/modules/net-vpc-firewall"
- project_id = "my-project"
- network = "my-network"
+ source = "./fabric/modules/net-vpc-firewall"
+ project_id = "my-project"
+ network = "my-network"
factories_config = {
- rules_folder = "configs/firewal/rules"
- cidr_tpl_file = "configs/firewal/cidr_template.yaml"
+ rules_folder = "configs/firewall/rules"
+ cidr_tpl_file = "configs/firewall/cidrs.yaml"
}
-
+ default_rules_config = { disabled = true }
}
-# tftest modules=1 resources=3
+# tftest modules=1 resources=3 files=lbs,cidrs
```
```yaml
-# tftest file configs/firewall/rules/load_balancers.yaml
-allow-healthchecks:
- description: Allow ingress from healthchecks.
- ranges:
- - healthchecks
- targets: ["lb-backends"]
- rules:
- - protocol: tcp
- ports:
- - 80
- - 443
+# tftest-file id=lbs path=configs/firewall/rules/load_balancers.yaml
+ingress:
+ allow-healthchecks:
+ description: Allow ingress from healthchecks.
+ source_ranges:
+ - healthchecks
+ targets: ["lb-backends"]
+ rules:
+ - protocol: tcp
+ ports:
+ - 80
+ - 443
+ allow-service-1-to-service-2:
+ description: Allow ingress from service-1 SA
+ targets: ["service-2"]
+ use_service_accounts: true
+ sources:
+ - service-1@my-project.iam.gserviceaccount.com
+ rules:
+ - protocol: tcp
+ ports:
+ - 80
+ - 443
+egress:
+ block-telnet:
+ description: block outbound telnet
+ deny: true
+ rules:
+ - protocol: tcp
+ ports:
+ - 23
```
```yaml
-# tftest file configs/firewall/cidr_template.yaml
+# tftest-file id=cidrs path=configs/firewall/cidrs.yaml
healthchecks:
- 35.191.0.0/16
- 130.211.0.0/22
@@ -174,13 +202,13 @@ healthchecks:
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [network](variables.tf#L109) | Name of the network this set of firewall rules applies to. | string | ✓ | |
-| [project_id](variables.tf#L114) | Project id of the project that holds the network. | string | ✓ | |
+| [network](variables.tf#L108) | Name of the network this set of firewall rules applies to. | string | ✓ | |
+| [project_id](variables.tf#L113) | Project id of the project that holds the network. | string | ✓ | |
| [default_rules_config](variables.tf#L17) | Optionally created convenience rules. Set the 'disabled' attribute to true, or individual rule attributes to empty lists to disable. | object({…}) | | {} |
-| [egress_rules](variables.tf#L37) | List of egress rule definitions, default to deny action. | map(object({…})) | | {} |
-| [factories_config](variables.tf#L60) | Paths to data files and folders that enable factory functionality. | object({…}) | | null |
-| [ingress_rules](variables.tf#L69) | List of ingress rule definitions, default to allow action. | map(object({…})) | | {} |
-| [named_ranges](variables.tf#L92) | Define mapping of names to ranges that can be used in custom rules. | map(list(string)) | | {…} |
+| [egress_rules](variables.tf#L37) | List of egress rule definitions, default to deny action. Null destination ranges will be replaced with 0/0. | map(object({…})) | | {} |
+| [factories_config](variables.tf#L59) | Paths to data files and folders that enable factory functionality. | object({…}) | | null |
+| [ingress_rules](variables.tf#L68) | List of ingress rule definitions, default to allow action. Null source ranges will be replaced with 0/0. | map(object({…})) | | {} |
+| [named_ranges](variables.tf#L91) | Define mapping of names to ranges that can be used in custom rules. | map(list(string)) | | {…} |
## Outputs
diff --git a/modules/net-vpc-firewall/main.tf b/modules/net-vpc-firewall/main.tf
index 708b8844b..e525ceb4b 100644
--- a/modules/net-vpc-firewall/main.tf
+++ b/modules/net-vpc-firewall/main.tf
@@ -66,15 +66,23 @@ locals {
for name, rule in local._rules :
name => merge(rule, {
action = rule.deny == true ? "DENY" : "ALLOW"
- destination_ranges = flatten([
- for range in coalesce(try(rule.destination_ranges, null), []) :
- try(local._named_ranges[range], range)
- ])
+ destination_ranges = (
+ try(rule.destination_ranges, null) == null
+ ? null
+ : flatten([
+ for range in rule.destination_ranges :
+ try(local._named_ranges[range], range)
+ ])
+ )
rules = { for k, v in rule.rules : k => v }
- source_ranges = flatten([
- for range in coalesce(try(rule.source_ranges, null), []) :
- try(local._named_ranges[range], range)
- ])
+ source_ranges = (
+ try(rule.source_ranges, null) == null
+ ? null
+ : flatten([
+ for range in rule.source_ranges :
+ try(local._named_ranges[range], range)
+ ])
+ )
})
}
}
@@ -89,18 +97,20 @@ resource "google_compute_firewall" "custom-rules" {
source_ranges = (
each.value.direction == "INGRESS"
? (
- coalesce(each.value.source_ranges, []) == []
+ each.value.source_ranges == null
? ["0.0.0.0/0"]
: each.value.source_ranges
- ) : null
+ )
+ : null
)
destination_ranges = (
each.value.direction == "EGRESS"
? (
- coalesce(each.value.destination_ranges, []) == []
+ each.value.destination_ranges == null
? ["0.0.0.0/0"]
: each.value.destination_ranges
- ) : null
+ )
+ : null
)
source_tags = (
each.value.use_service_accounts || each.value.direction == "EGRESS"
diff --git a/modules/net-vpc-firewall/variables.tf b/modules/net-vpc-firewall/variables.tf
index 3e458acd8..9f750cc83 100644
--- a/modules/net-vpc-firewall/variables.tf
+++ b/modules/net-vpc-firewall/variables.tf
@@ -35,7 +35,7 @@ variable "default_rules_config" {
}
variable "egress_rules" {
- description = "List of egress rule definitions, default to deny action."
+ description = "List of egress rule definitions, default to deny action. Null destination ranges will be replaced with 0/0."
type = map(object({
deny = optional(bool, true)
description = optional(string)
@@ -45,7 +45,6 @@ variable "egress_rules" {
include_metadata = optional(bool)
}))
priority = optional(number, 1000)
- sources = optional(list(string))
targets = optional(list(string))
use_service_accounts = optional(bool, false)
rules = optional(list(object({
@@ -67,7 +66,7 @@ variable "factories_config" {
}
variable "ingress_rules" {
- description = "List of ingress rule definitions, default to allow action."
+ description = "List of ingress rule definitions, default to allow action. Null source ranges will be replaced with 0/0."
type = map(object({
deny = optional(bool, false)
description = optional(string)
diff --git a/modules/net-vpc-firewall/versions.tf b/modules/net-vpc-firewall/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/net-vpc-firewall/versions.tf
+++ b/modules/net-vpc-firewall/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/net-vpc-peering/versions.tf b/modules/net-vpc-peering/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/net-vpc-peering/versions.tf
+++ b/modules/net-vpc-peering/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md
index b59694062..dbd855022 100644
--- a/modules/net-vpc/README.md
+++ b/modules/net-vpc/README.md
@@ -30,7 +30,88 @@ module "vpc" {
}
]
}
-# tftest modules=1 resources=3
+# tftest modules=1 resources=3 inventory=simple.yaml
+```
+
+### Subnet Options
+```hcl
+module "vpc" {
+ source = "./fabric/modules/net-vpc"
+ project_id = "my-project"
+ name = "my-network"
+ subnets = [
+ # simple subnet
+ {
+ name = "simple"
+ region = "europe-west1"
+ ip_cidr_range = "10.0.0.0/24"
+ },
+ # custom description and PGA disabled
+ {
+ name = "no-pga"
+ region = "europe-west1"
+ ip_cidr_range = "10.0.1.0/24",
+ description = "Subnet b"
+ enable_private_access = false
+ },
+ # secondary ranges
+ {
+ name = "with-secondary-ranges"
+ region = "europe-west1"
+ ip_cidr_range = "10.0.2.0/24"
+ secondary_ip_ranges = {
+ a = "192.168.0.0/24"
+ b = "192.168.1.0/24"
+ }
+ },
+ # enable flow logs
+ {
+ name = "with-flow-logs"
+ region = "europe-west1"
+ ip_cidr_range = "10.0.3.0/24"
+ flow_logs_config = {
+ flow_sampling = 0.5
+ aggregation_interval = "INTERVAL_10_MIN"
+ }
+ }
+ ]
+}
+# tftest modules=1 resources=5 inventory=subnet-options.yaml
+```
+
+### Subnet IAM
+
+```hcl
+module "vpc" {
+ source = "./fabric/modules/net-vpc"
+ project_id = "my-project"
+ name = "my-network"
+ subnets = [
+ {
+ name = "subnet-1"
+ region = "europe-west1"
+ ip_cidr_range = "10.0.1.0/24"
+ },
+ {
+ name = "subnet-2"
+ region = "europe-west1"
+ ip_cidr_range = "10.0.1.0/24"
+ }
+ ]
+ subnet_iam = {
+ "europe-west1/subnet-1" = {
+ "roles/compute.networkUser" = [
+ "user:user1@example.com", "group:group1@example.com"
+ ]
+ }
+ "europe-west1/subnet-2" = {
+ "roles/compute.networkUser" = [
+ "user:user2@example.com", "group:group2@example.com"
+ ]
+ }
+ }
+}
+# tftest modules=1 resources=5 inventory=subnet-iam.yaml
```
### Peering
@@ -45,9 +126,9 @@ module "vpc-hub" {
project_id = "hub"
name = "vpc-hub"
subnets = [{
- ip_cidr_range = "10.0.0.0/24"
- name = "subnet-1"
- region = "europe-west1"
+ ip_cidr_range = "10.0.0.0/24"
+ name = "subnet-1"
+ region = "europe-west1"
}]
}
@@ -56,16 +137,16 @@ module "vpc-spoke-1" {
project_id = "spoke1"
name = "vpc-spoke1"
subnets = [{
- ip_cidr_range = "10.0.1.0/24"
- name = "subnet-2"
- region = "europe-west1"
+ ip_cidr_range = "10.0.1.0/24"
+ name = "subnet-2"
+ region = "europe-west1"
}]
peering_config = {
peer_vpc_self_link = module.vpc-hub.self_link
import_routes = true
}
}
-# tftest modules=2 resources=6
+# tftest modules=2 resources=6 inventory=peering.yaml
```
### Shared VPC
@@ -75,9 +156,9 @@ module "vpc-spoke-1" {
```hcl
locals {
service_project_1 = {
- project_id = "project1"
- gke_service_account = "gke"
- cloud_services_service_account = "cloudsvc"
+ project_id = "project1"
+ gke_service_account = "serviceAccount:gke"
+ cloud_services_service_account = "serviceAccount:cloudsvc"
}
service_project_2 = {
project_id = "project2"
@@ -116,7 +197,7 @@ module "vpc-host" {
}
}
}
-# tftest modules=1 resources=7
+# tftest modules=1 resources=7 inventory=shared-vpc.yaml
```
### Private Service Networking
@@ -128,16 +209,16 @@ module "vpc" {
name = "my-network"
subnets = [
{
- ip_cidr_range = "10.0.0.0/24"
- name = "production"
- region = "europe-west1"
+ ip_cidr_range = "10.0.0.0/24"
+ name = "production"
+ region = "europe-west1"
}
]
psa_config = {
ranges = { myrange = "10.0.1.0/24" }
}
}
-# tftest modules=1 resources=5
+# tftest modules=1 resources=5 inventory=psc.yaml
```
### Private Service Networking with peering routes
@@ -151,18 +232,18 @@ module "vpc" {
name = "my-network"
subnets = [
{
- ip_cidr_range = "10.0.0.0/24"
- name = "production"
- region = "europe-west1"
+ ip_cidr_range = "10.0.0.0/24"
+ name = "production"
+ region = "europe-west1"
}
]
psa_config = {
- ranges = { myrange = "10.0.1.0/24" }
+ ranges = { myrange = "10.0.1.0/24" }
export_routes = true
import_routes = true
}
}
-# tftest modules=1 resources=5
+# tftest modules=1 resources=5 inventory=psc-routes.yaml
```
### Subnets for Private Service Connect, Proxy-only subnets
@@ -194,7 +275,7 @@ module "vpc" {
}
]
}
-# tftest modules=1 resources=3
+# tftest modules=1 resources=3 inventory=proxy-only-subnets.yaml
```
### DNS Policies
@@ -205,7 +286,7 @@ module "vpc" {
project_id = "my-project"
name = "my-network"
dns_policy = {
- inbound = true
+ inbound = true
outbound = {
private_ns = ["10.0.0.1"]
public_ns = ["8.8.8.8"]
@@ -213,13 +294,13 @@ module "vpc" {
}
subnets = [
{
- ip_cidr_range = "10.0.0.0/24"
- name = "production"
- region = "europe-west1"
+ ip_cidr_range = "10.0.0.0/24"
+ name = "production"
+ region = "europe-west1"
}
]
}
-# tftest modules=1 resources=3
+# tftest modules=1 resources=3 inventory=dns-policies.yaml
```
### Subnet Factory
@@ -233,11 +314,17 @@ module "vpc" {
name = "my-network"
data_folder = "config/subnets"
}
-# tftest modules=1 resources=1 file=subnets
+# tftest modules=1 resources=3 files=subnet-simple,subnet-detailed inventory=factory.yaml
```
```yaml
-# tftest file subnets ./config/subnets/subnet-name.yaml
+# tftest-file id=subnet-simple path=config/subnets/subnet-simple.yaml
+region: europe-west4
+ip_cidr_range: 10.0.1.0/24
+```
+
+```yaml
+# tftest-file id=subnet-detailed path=config/subnets/subnet-detailed.yaml
region: europe-west1
description: Sample description
ip_cidr_range: 10.0.0.0/24
@@ -249,11 +336,50 @@ iam_service_accounts: ["fbz@prj.iam.gserviceaccount.com"]
secondary_ip_ranges: # map of secondary ip ranges
secondary-range-a: 192.168.0.0/24
flow_logs: # enable, set to empty map to use defaults
- - aggregation_interval: "INTERVAL_5_SEC"
- - flow_sampling: 0.5
- - metadata: "INCLUDE_ALL_METADATA"
+ aggregation_interval: "INTERVAL_5_SEC"
+ flow_sampling: 0.5
+ metadata: "INCLUDE_ALL_METADATA"
+ filter_expression: null
```
-
+
+### Custom Routes
+
+VPC routes can be configured through the `routes` variable.
+
+```hcl
+locals {
+ route_types = {
+ gateway = "global/gateways/default-internet-gateway"
+ instance = "zones/europe-west1-b/test"
+ ip = "192.168.0.128"
+ ilb = "regions/europe-west1/forwardingRules/test"
+ vpn_tunnel = "regions/europe-west1/vpnTunnels/foo"
+ }
+}
+module "vpc" {
+ source = "./fabric/modules/net-vpc"
+ for_each = local.route_types
+ project_id = "my-project"
+ name = "my-network-with-route-${replace(each.key, "_", "-")}"
+ routes = {
+ next-hop = {
+ dest_range = "192.168.128.0/24"
+ tags = null
+ next_hop_type = each.key
+ next_hop = each.value
+ }
+ gateway = {
+ dest_range = "0.0.0.0/0",
+ priority = 100
+ tags = ["tag-a"]
+ next_hop_type = "gateway",
+ next_hop = "global/gateways/default-internet-gateway"
+ }
+ }
+}
+# tftest modules=5 resources=15 inventory=routes.yaml
+```
+
## Variables
diff --git a/modules/net-vpc/main.tf b/modules/net-vpc/main.tf
index 7eedc95ac..d15058017 100644
--- a/modules/net-vpc/main.tf
+++ b/modules/net-vpc/main.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -109,7 +109,7 @@ resource "google_dns_policy" "default" {
)
iterator = ns
content {
- ipv4_address = ns.key
+ ipv4_address = ns.value
forwarding_path = "private"
}
}
@@ -121,7 +121,7 @@ resource "google_dns_policy" "default" {
)
iterator = ns
content {
- ipv4_address = ns.key
+ ipv4_address = ns.value
}
}
}
diff --git a/modules/net-vpc/versions.tf b/modules/net-vpc/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/net-vpc/versions.tf
+++ b/modules/net-vpc/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/net-vpn-dynamic/README.md b/modules/net-vpn-dynamic/README.md
index 378ba6b7b..447e5652c 100644
--- a/modules/net-vpn-dynamic/README.md
+++ b/modules/net-vpn-dynamic/README.md
@@ -23,11 +23,11 @@ module "vm" {
module "vpn-dynamic" {
- source = "./fabric/modules/net-vpn-dynamic"
- project_id = "my-project"
- region = "europe-west1"
- network = var.vpc.name
- name = "gateway-1"
+ source = "./fabric/modules/net-vpn-dynamic"
+ project_id = "my-project"
+ region = "europe-west1"
+ network = var.vpc.name
+ name = "gateway-1"
router_config = {
asn = 64514
}
diff --git a/modules/net-vpn-dynamic/versions.tf b/modules/net-vpn-dynamic/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/net-vpn-dynamic/versions.tf
+++ b/modules/net-vpn-dynamic/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/net-vpn-ha/README.md b/modules/net-vpn-ha/README.md
index e6cd752c2..0b7b52903 100644
--- a/modules/net-vpn-ha/README.md
+++ b/modules/net-vpn-ha/README.md
@@ -13,7 +13,7 @@ module "vpn-1" {
name = "net1-to-net-2"
peer_gateway = { gcp = module.vpn-2.self_link }
router_config = {
- asn = 64514
+ asn = 64514
custom_advertise = {
all_subnets = true
ip_ranges = {
@@ -48,7 +48,7 @@ module "vpn-2" {
network = var.vpc2.self_link
name = "net2-to-net1"
router_config = { asn = 64513 }
- peer_gateway = { gcp = module.vpn-1.self_link}
+ peer_gateway = { gcp = module.vpn-1.self_link }
tunnels = {
remote-0 = {
bgp_peer = {
diff --git a/modules/net-vpn-ha/versions.tf b/modules/net-vpn-ha/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/net-vpn-ha/versions.tf
+++ b/modules/net-vpn-ha/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/net-vpn-static/versions.tf b/modules/net-vpn-static/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/net-vpn-static/versions.tf
+++ b/modules/net-vpn-static/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/organization/README.md b/modules/organization/README.md
index 2e24c91bd..b6caa3cd0 100644
--- a/modules/organization/README.md
+++ b/modules/organization/README.md
@@ -16,22 +16,14 @@ To manage organization policies, the `orgpolicy.googleapis.com` service should b
module "org" {
source = "./fabric/modules/organization"
organization_id = "organizations/1234567890"
- group_iam = {
+ group_iam = {
"cloud-owners@example.org" = ["roles/owner", "roles/projectCreator"]
}
- iam = {
+ iam = {
"roles/resourcemanager.projectCreator" = ["group:cloud-admins@example.org"]
}
-
- org_policy_custom_constraints = {
- "custom.gkeEnableAutoUpgrade" = {
- resource_types = ["container.googleapis.com/NodePool"]
- method_types = ["CREATE"]
- condition = "resource.management.autoUpgrade == true"
- action_type = "ALLOW"
- display_name = "Enable node auto-upgrade"
- description = "All node pools must have node auto-upgrade enabled."
- }
+ iam_additive_members = {
+ "user:compute@example.org" = ["roles/compute.admin", "roles/container.viewer"]
}
org_policies = {
@@ -76,7 +68,7 @@ module "org" {
}
}
}
-# tftest modules=1 resources=12
+# tftest modules=1 resources=13 inventory=basic.yaml
```
## IAM
@@ -104,7 +96,7 @@ To manage organization policy custom constraints, the `orgpolicy.googleapis.com`
module "org" {
source = "./fabric/modules/organization"
organization_id = var.organization_id
-
+
org_policy_custom_constraints = {
"custom.gkeEnableAutoUpgrade" = {
resource_types = ["container.googleapis.com/NodePool"]
@@ -123,7 +115,7 @@ module "org" {
}
}
}
-# tftest modules=1 resources=2
+# tftest modules=1 resources=2 inventory=custom-constraints.yaml
```
### Org policy custom constraints factory
@@ -134,16 +126,20 @@ The example below deploys a few org policy custom constraints split between two
```hcl
module "org" {
- source = "./fabric/modules/organization"
- organization_id = var.organization_id
-
+ source = "./fabric/modules/organization"
+ organization_id = var.organization_id
org_policy_custom_constraints_data_path = "configs/custom-constraints"
+ org_policies = {
+ "custom.gkeEnableAutoUpgrade" = {
+ enforce = true
+ }
+ }
}
-# tftest modules=1 resources=3 files=gke,dataproc
+# tftest modules=1 resources=3 files=gke inventory=custom-constraints.yaml
```
```yaml
-# tftest file gke configs/custom-constraints/gke.yaml
+# tftest-file id=gke path=configs/custom-constraints/gke.yaml
custom.gkeEnableLogging:
resource_types:
- container.googleapis.com/Cluster
@@ -164,8 +160,9 @@ custom.gkeEnableAutoUpgrade:
description: All node pools must have node auto-upgrade enabled.
```
+
```yaml
-# tftest file dataproc configs/custom-constraints/dataproc.yaml
+# tftest-file id=dataproc path=configs/custom-constraints/dataproc.yaml
custom.dataprocNoMoreThan10Workers:
resource_types:
- dataproc.googleapis.com/Cluster
@@ -195,6 +192,17 @@ module "org" {
organization_id = var.organization_id
firewall_policies = {
iap-policy = {
+ allow-admins = {
+ description = "Access from the admin subnet to all subnets"
+ direction = "INGRESS"
+ action = "allow"
+ priority = 1000
+ ranges = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
+ ports = { all = [] }
+ target_service_accounts = null
+ target_resources = null
+ logging = false
+ }
allow-iap-ssh = {
description = "Always allow ssh from IAP."
direction = "INGRESS"
@@ -214,7 +222,7 @@ module "org" {
iap_policy = "iap-policy"
}
}
-# tftest modules=1 resources=3
+# tftest modules=1 resources=4 inventory=hfw.yaml
```
### Firewall policy factory
@@ -227,18 +235,18 @@ module "org" {
organization_id = var.organization_id
firewall_policy_factory = {
cidr_file = "configs/firewall-policies/cidrs.yaml"
- policy_name = null
+ policy_name = "iap-policy"
rules_file = "configs/firewall-policies/rules.yaml"
}
firewall_policy_association = {
- factory-policy = module.org.firewall_policy_id["factory"]
+ iap_policy = module.org.firewall_policy_id["iap-policy"]
}
}
-# tftest modules=1 resources=4 files=cidrs,rules
+# tftest modules=1 resources=4 files=cidrs,rules inventory=hfw.yaml
```
```yaml
-# tftest file cidrs configs/firewall-policies/cidrs.yaml
+# tftest-file id=cidrs path=configs/firewall-policies/cidrs.yaml
rfc1918:
- 10.0.0.0/8
- 172.16.0.0/12
@@ -246,7 +254,7 @@ rfc1918:
```
```yaml
-# tftest file rules configs/firewall-policies/rules.yaml
+# tftest-file id=rules path=configs/firewall-policies/rules.yaml
allow-admins:
description: Access from the admin subnet to all subnets
direction: INGRESS
@@ -257,19 +265,19 @@ allow-admins:
ports:
all: []
target_resources: null
- enable_logging: false
+ logging: false
-allow-ssh-from-iap:
- description: Enable SSH from IAP
+allow-iap-ssh:
+ description: "Always allow ssh from IAP."
direction: INGRESS
action: allow
- priority: 1002
+ priority: 100
ranges:
- 35.235.240.0/20
ports:
tcp: ["22"]
target_resources: null
- enable_logging: false
+ logging: false
```
## Logging Sinks
@@ -325,7 +333,7 @@ module "org" {
debug = {
destination = module.bucket.id
filter = "severity=DEBUG"
- exclusions = {
+ exclusions = {
no-compute = "logName:compute"
}
type = "logging"
@@ -335,7 +343,7 @@ module "org" {
no-gce-instances = "resource.type=gce_instance"
}
}
-# tftest modules=5 resources=13
+# tftest modules=5 resources=13 inventory=logging.yaml
```
## Custom Roles
@@ -353,7 +361,7 @@ module "org" {
(module.org.custom_role_id.myRole) = ["user:me@example.com"]
}
}
-# tftest modules=1 resources=2
+# tftest modules=1 resources=2 inventory=roles.yaml
```
## Tags
@@ -366,12 +374,12 @@ module "org" {
organization_id = var.organization_id
tags = {
environment = {
- description = "Environment specification."
- iam = {
+ description = "Environment specification."
+ iam = {
"roles/resourcemanager.tagAdmin" = ["group:admins@example.com"]
}
values = {
- dev = {}
+ dev = {}
prod = {
description = "Environment: production."
iam = {
@@ -386,7 +394,7 @@ module "org" {
foo = "tagValues/12345678"
}
}
-# tftest modules=1 resources=7
+# tftest modules=1 resources=7 inventory=tags.yaml
```
You can also define network tags, through a dedicated variable *network_tags*:
@@ -397,13 +405,13 @@ module "org" {
organization_id = var.organization_id
network_tags = {
net-environment = {
- description = "This is a network tag."
- network = "my_project/my_vpc"
- iam = {
+ description = "This is a network tag."
+ network = "my_project/my_vpc"
+ iam = {
"roles/resourcemanager.tagAdmin" = ["group:admins@example.com"]
}
values = {
- dev = null
+ dev = null
prod = {
description = "Environment: production."
iam = {
@@ -414,7 +422,7 @@ module "org" {
}
}
}
-# tftest modules=1 resources=5
+# tftest modules=1 resources=5 inventory=network-tags.yaml
```
@@ -454,13 +462,13 @@ module "org" {
| [iam_bindings_authoritative](variables.tf#L116) | IAM authoritative bindings, in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared. Bindings should also be authoritative when using authoritative audit config. Use with caution. | map(list(string)) | | null |
| [logging_exclusions](variables.tf#L122) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string) | | {} |
| [logging_sinks](variables.tf#L129) | Logging sinks to create for the organization. | map(object({…})) | | {} |
-| [network_tags](variables.tf#L159) | Network tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} |
-| [org_policies](variables.tf#L180) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} |
+| [network_tags](variables.tf#L159) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} |
+| [org_policies](variables.tf#L181) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} |
| [org_policies_data_path](variables.tf#L220) | Path containing org policies in YAML format. | string | | null |
| [org_policy_custom_constraints](variables.tf#L226) | Organization policiy custom constraints keyed by constraint name. | map(object({…})) | | {} |
| [org_policy_custom_constraints_data_path](variables.tf#L240) | Path containing org policy custom constraints in YAML format. | string | | null |
| [tag_bindings](variables.tf#L255) | Tag bindings for this organization, in key => tag value id format. | map(string) | | null |
-| [tags](variables.tf#L261) | Tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} |
+| [tags](variables.tf#L261) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} |
## Outputs
@@ -472,9 +480,9 @@ module "org" {
| [firewall_policy_id](outputs.tf#L40) | Map of firewall policy ids created in the organization. | |
| [network_tag_keys](outputs.tf#L45) | Tag key resources. | |
| [network_tag_values](outputs.tf#L54) | Tag value resources. | |
-| [organization_id](outputs.tf#L65) | Organization id dependent on module resources. | |
-| [sink_writer_identities](outputs.tf#L82) | Writer identities created for each sink. | |
-| [tag_keys](outputs.tf#L90) | Tag key resources. | |
-| [tag_values](outputs.tf#L99) | Tag value resources. | |
+| [organization_id](outputs.tf#L62) | Organization id dependent on module resources. | |
+| [sink_writer_identities](outputs.tf#L79) | Writer identities created for each sink. | |
+| [tag_keys](outputs.tf#L87) | Tag key resources. | |
+| [tag_values](outputs.tf#L96) | Tag value resources. | |
diff --git a/modules/organization/organization-policies.tf b/modules/organization/organization-policies.tf
index 62d464557..1a99ef9a1 100644
--- a/modules/organization/organization-policies.tf
+++ b/modules/organization/organization-policies.tf
@@ -95,23 +95,6 @@ resource "google_org_policy_policy" "default" {
inherit_from_parent = each.value.inherit_from_parent
reset = each.value.reset
- rules {
- allow_all = try(each.value.allow.all, null) == true ? "TRUE" : null
- deny_all = try(each.value.deny.all, null) == true ? "TRUE" : null
- enforce = (
- each.value.is_boolean_policy && each.value.enforce != null
- ? upper(tostring(each.value.enforce))
- : null
- )
- dynamic "values" {
- for_each = each.value.has_values ? [1] : []
- content {
- allowed_values = try(each.value.allow.values, null)
- denied_values = try(each.value.deny.values, null)
- }
- }
- }
-
dynamic "rules" {
for_each = each.value.rules
iterator = rule
@@ -138,6 +121,23 @@ resource "google_org_policy_policy" "default" {
}
}
}
+
+ rules {
+ allow_all = try(each.value.allow.all, null) == true ? "TRUE" : null
+ deny_all = try(each.value.deny.all, null) == true ? "TRUE" : null
+ enforce = (
+ each.value.is_boolean_policy && each.value.enforce != null
+ ? upper(tostring(each.value.enforce))
+ : null
+ )
+ dynamic "values" {
+ for_each = each.value.has_values ? [1] : []
+ content {
+ allowed_values = try(each.value.allow.values, null)
+ denied_values = try(each.value.deny.values, null)
+ }
+ }
+ }
}
depends_on = [
diff --git a/modules/organization/outputs.tf b/modules/organization/outputs.tf
index 40d84b473..2e594ee66 100644
--- a/modules/organization/outputs.tf
+++ b/modules/organization/outputs.tf
@@ -54,11 +54,8 @@ output "network_tag_keys" {
output "network_tag_values" {
description = "Tag value resources."
value = {
- for k, v in google_tags_tag_value.default
- : k => v if(
- google_tags_tag_key.default[split("/", k)[0]].purpose != null &&
- google_tags_tag_key.default[split("/", k)[0]].purpose != ""
- )
+ for k, v in google_tags_tag_value.default :
+ k => v if local.tag_values[k].tag_network
}
}
@@ -99,10 +96,7 @@ output "tag_keys" {
output "tag_values" {
description = "Tag value resources."
value = {
- for k, v in google_tags_tag_value.default
- : k => v if(
- google_tags_tag_key.default[split("/", k)[0]].purpose == null ||
- google_tags_tag_key.default[split("/", k)[0]].purpose == ""
- )
+ for k, v in google_tags_tag_value.default :
+ k => v if !local.tag_values[k].tag_network
}
}
diff --git a/modules/organization/tags.tf b/modules/organization/tags.tf
index 544b8989f..b579479ed 100644
--- a/modules/organization/tags.tf
+++ b/modules/organization/tags.tf
@@ -23,17 +23,21 @@ locals {
"Managed by the Terraform organization module."
)
key = "${tag}/${value}"
+ id = try(value_attrs.id, null)
name = value
roles = keys(coalesce(
value_attrs == null ? null : value_attrs.iam, {}
))
- tag = tag
+ tag = tag
+ tag_id = attrs.id
+ tag_network = try(attrs.network, null) != null
}
]
])
_tag_values_iam = flatten([
for key, value_attrs in local.tag_values : [
for role in value_attrs.roles : {
+ id = value_attrs.id
key = value_attrs.key
name = value_attrs.name
role = role
@@ -44,8 +48,9 @@ locals {
_tags_iam = flatten([
for tag, attrs in local.tags : [
for role in keys(coalesce(attrs.iam, {})) : {
- role = role
- tag = tag
+ role = role
+ tag = tag
+ tag_id = attrs.id
}
]
])
@@ -64,7 +69,7 @@ locals {
# keys
resource "google_tags_tag_key" "default" {
- for_each = local.tags
+ for_each = { for k, v in local.tags : k => v if v.id == null }
parent = var.organization_id
purpose = (
lookup(each.value, "network", null) == null ? null : "GCE_FIREWALL"
@@ -83,8 +88,12 @@ resource "google_tags_tag_key" "default" {
resource "google_tags_tag_key_iam_binding" "default" {
for_each = local.tags_iam
- tag_key = google_tags_tag_key.default[each.value.tag].id
- role = each.value.role
+ tag_key = (
+ each.value.tag_id == null
+ ? google_tags_tag_key.default[each.value.tag].id
+ : each.value.tag_id
+ )
+ role = each.value.role
members = coalesce(
local.tags[each.value.tag]["iam"][each.value.role], []
)
@@ -93,16 +102,24 @@ resource "google_tags_tag_key_iam_binding" "default" {
# values
resource "google_tags_tag_value" "default" {
- for_each = local.tag_values
- parent = google_tags_tag_key.default[each.value.tag].id
+ for_each = { for k, v in local.tag_values : k => v if v.id == null }
+ parent = (
+ each.value.tag_id == null
+ ? google_tags_tag_key.default[each.value.tag].id
+ : each.value.tag_id
+ )
short_name = each.value.name
description = each.value.description
}
resource "google_tags_tag_value_iam_binding" "default" {
- for_each = local.tag_values_iam
- tag_value = google_tags_tag_value.default[each.value.key].id
- role = each.value.role
+ for_each = local.tag_values_iam
+ tag_value = (
+ each.value.id == null
+ ? google_tags_tag_value.default[each.value.key].id
+ : each.value.id
+ )
+ role = each.value.role
members = coalesce(
local.tags[each.value.tag]["values"][each.value.name]["iam"][each.value.role],
[]
diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf
index 84c81ff5b..ced5cad3d 100644
--- a/modules/organization/variables.tf
+++ b/modules/organization/variables.tf
@@ -157,10 +157,11 @@ variable "logging_sinks" {
}
variable "network_tags" {
- description = "Network tags by key name. The `iam` attribute behaves like the similarly named one at module level."
+ description = "Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level."
type = map(object({
description = optional(string, "Managed by the Terraform organization module.")
iam = optional(map(list(string)), {})
+ id = optional(string)
network = string # project_id/vpc_name
values = optional(map(object({
description = optional(string, "Managed by the Terraform organization module.")
@@ -193,7 +194,6 @@ variable "org_policies" {
values = optional(list(string))
}))
enforce = optional(bool, true) # for boolean policies only.
-
# conditional values
rules = optional(list(object({
allow = optional(object({
@@ -259,13 +259,15 @@ variable "tag_bindings" {
}
variable "tags" {
- description = "Tags by key name. The `iam` attribute behaves like the similarly named one at module level."
+ description = "Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level."
type = map(object({
description = optional(string, "Managed by the Terraform organization module.")
iam = optional(map(list(string)), {})
+ id = optional(string)
values = optional(map(object({
description = optional(string, "Managed by the Terraform organization module.")
iam = optional(map(list(string)), {})
+ id = optional(string)
})), {})
}))
nullable = false
diff --git a/modules/organization/versions.tf b/modules/organization/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/organization/versions.tf
+++ b/modules/organization/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/project/README.md b/modules/project/README.md
index b6fb3881c..e7a645fe5 100644
--- a/modules/project/README.md
+++ b/modules/project/README.md
@@ -2,6 +2,23 @@
This module implements the creation and management of one GCP project including IAM, organization policies, Shared VPC host or service attachment, service API activation, and tag attachment. It also offers a convenient way to refer to managed service identities (aka robot service accounts) for APIs.
+# Basic Project Creation
+
+```hcl
+module "project" {
+ source = "./fabric/modules/project"
+ billing_account = "123456-123456-123456"
+ name = "myproject"
+ parent = "folders/1234567890"
+ prefix = "foo"
+ services = [
+ "container.googleapis.com",
+ "stackdriver.googleapis.com"
+ ]
+}
+# tftest modules=1 resources=3 inventory=basic.yaml
+```
+
## IAM Examples
IAM is managed via several variables that implement different levels of control:
@@ -26,7 +43,7 @@ module "project" {
name = "project-example"
parent = "folders/1234567890"
prefix = "foo"
- services = [
+ services = [
"container.googleapis.com",
"stackdriver.googleapis.com"
]
@@ -36,7 +53,7 @@ module "project" {
]
}
}
-# tftest modules=1 resources=4
+# tftest modules=1 resources=4 inventory=iam-authoritative.yaml
```
The `group_iam` variable uses group email addresses as keys and is a convenient way to assign roles to humans following Google's best practices. The end result is readable code that also serves as documentation.
@@ -48,10 +65,6 @@ module "project" {
name = "project-example"
parent = "folders/1234567890"
prefix = "foo"
- services = [
- "container.googleapis.com",
- "stackdriver.googleapis.com"
- ]
group_iam = {
"gcp-security-admins@example.com" = [
"roles/cloudasset.owner",
@@ -61,7 +74,7 @@ module "project" {
]
}
}
-# tftest modules=1 resources=7
+# tftest modules=1 resources=5 inventory=iam-group.yaml
```
### Additive IAM
@@ -70,22 +83,37 @@ Additive IAM is typically used where bindings for specific roles are controlled
```hcl
module "project" {
- source = "./fabric/modules/project"
- name = "project-example"
+ source = "./fabric/modules/project"
+ name = "project-example"
iam_additive = {
- "roles/viewer" = [
+ "roles/viewer" = [
"group:one@example.org",
"group:two@xample.org"
],
- "roles/storage.objectAdmin" = [
+ "roles/storage.objectAdmin" = [
"group:two@example.org"
],
- "roles/owner" = [
+ "roles/owner" = [
"group:three@example.org"
],
}
}
-# tftest modules=1 resources=5
+# tftest modules=1 resources=5 inventory=iam-additive.yaml
+```
+
+### Additive IAM by members
+
+```hcl
+module "project" {
+ source = "./fabric/modules/project"
+ name = "project-example"
+ iam_additive_members = {
+ "user:one@example.org" = ["roles/owner"]
+ "user:two@example.org" = ["roles/owner", "roles/editor"]
+ }
+
+}
+# tftest modules=1 resources=4 inventory=iam-additive-members.yaml
```
### Service Identities and authoritative IAM
@@ -94,15 +122,15 @@ As mentioned above, there are cases where authoritative management of specific I
```hcl
module "project" {
- source = "./fabric/modules/project"
- name = "project-example"
+ source = "./fabric/modules/project"
+ name = "project-example"
group_iam = {
"foo@example.com" = [
"roles/editor"
]
}
iam = {
- "roles/editor" = [
+ "roles/editor" = [
"serviceAccount:${module.project.service_accounts.cloud_services}"
]
}
@@ -110,39 +138,88 @@ module "project" {
# tftest modules=1 resources=2
```
-## Shared VPC service
-
-The module allows managing Shared VPC status for both hosts and service projects, and includes a simple way of assigning Shared VPC roles to service identities.
-
-### Host project
-
-You can enable Shared VPC Host at the project level and manage project service association independently.
+### Using shortcodes for Service Identities in additive IAM
+Most Service Identities contains project number in their e-mail address and this prevents additive IAM to work, as these values are not known at moment of execution of `terraform plan` (its not an issue for authoritative IAM). To refer current project Service Identities you may use shortcodes for Service Identities similarly as for `service_identity_iam` when configuring Shared VPC.
```hcl
module "project" {
- source = "./fabric/modules/project"
- name = "project-example"
- shared_vpc_host_config = {
- enabled = true
+ source = "./fabric/modules/project"
+ name = "project-example"
+
+ services = [
+ "run.googleapis.com",
+ "container.googleapis.com",
+ ]
+
+ iam_additive = {
+ "roles/editor" = ["cloudservices"]
+ "roles/vpcaccess.user" = ["cloudrun"]
+ "roles/container.hostServiceAgentUser" = ["container-engine"]
+ }
+}
+# tftest modules=1 resources=6
+```
+
+
+### Service identities requiring manual IAM grants
+
+The module will create service identities at project creation instead of creating of them at the time of first use. This allows granting these service identities roles in other projects, something which is usually necessary in a Shared VPC context.
+
+You can grant roles to service identities using the following construct:
+
+```hcl
+module "project" {
+ source = "./fabric/modules/project"
+ name = "project-example"
+ iam = {
+ "roles/apigee.serviceAgent" = [
+ "serviceAccount:${module.project.service_accounts.robots.apigee}"
+ ]
}
}
# tftest modules=1 resources=2
```
-### Service project
+This table lists all affected services and roles that you need to grant to service identities
+
+| service | service identity | role |
+|---|---|---|
+| apigee.googleapis.com | apigee | roles/apigee.serviceAgent |
+| artifactregistry.googleapis.com | artifactregistry | roles/artifactregistry.serviceAgent |
+| cloudasset.googleapis.com | cloudasset | roles/cloudasset.serviceAgent |
+| cloudbuild.googleapis.com | cloudbuild | roles/cloudbuild.builds.builder |
+| gkehub.googleapis.com | fleet | roles/gkehub.serviceAgent |
+| multiclusteringress.googleapis.com | multicluster-ingress | roles/multiclusteringress.serviceAgent |
+| pubsub.googleapis.com | pubsub | roles/pubsub.serviceAgent |
+| sqladmin.googleapis.com | sqladmin | roles/cloudsql.serviceAgent |
+
+
+## Shared VPC
+
+The module allows managing Shared VPC status for both hosts and service projects, and includes a simple way of assigning Shared VPC roles to service identities.
+
+You can enable Shared VPC Host at the project level and manage project service association independently.
```hcl
-module "project" {
- source = "./fabric/modules/project"
- name = "project-example"
+module "host-project" {
+ source = "./fabric/modules/project"
+ name = "my-host-project"
+ shared_vpc_host_config = {
+ enabled = true
+ }
+}
+
+module "service-project" {
+ source = "./fabric/modules/project"
+ name = "my-service-project"
shared_vpc_service_config = {
- attach = true
- host_project = "my-host-project"
+ attach = true
+ host_project = module.host-project.project_id
service_identity_iam = {
- "roles/compute.networkUser" = [
+ "roles/compute.networkUser" = [
"cloudservices", "container-engine"
]
- "roles/vpcaccess.user" = [
+ "roles/vpcaccess.user" = [
"cloudrun"
]
"roles/container.hostServiceAgentUser" = [
@@ -151,7 +228,7 @@ module "project" {
}
}
}
-# tftest modules=1 resources=6
+# tftest modules=2 resources=8 inventory=shared-vpc.yaml
```
## Organization policies
@@ -165,10 +242,6 @@ module "project" {
name = "project-example"
parent = "folders/1234567890"
prefix = "foo"
- services = [
- "container.googleapis.com",
- "stackdriver.googleapis.com"
- ]
org_policies = {
"compute.disableGuestAttributesAccess" = {
enforce = true
@@ -208,7 +281,7 @@ module "project" {
}
}
}
-# tftest modules=1 resources=10
+# tftest modules=1 resources=8 inventory=org-policies.yaml
```
### Organization policy factory
@@ -220,63 +293,54 @@ Note that contraints defined via `org_policies` take precedence over those in `o
The example below deploys a few organization policies split between two YAML files.
```hcl
-module "folder" {
- source = "./fabric/modules/folder"
- parent = "organizations/1234567890"
- name = "Folder name"
+module "project" {
+ source = "./fabric/modules/project"
+ billing_account = "123456-123456-123456"
+ name = "project-example"
+ parent = "folders/1234567890"
+ prefix = "foo"
org_policies_data_path = "configs/org-policies/"
}
-# tftest modules=1 resources=6 files=boolean,list
+# tftest modules=1 resources=8 files=boolean,list inventory=org-policies.yaml
```
```yaml
-# tftest file boolean configs/org-policies/boolean.yaml
+# tftest-file id=boolean path=configs/org-policies/boolean.yaml
+compute.disableGuestAttributesAccess:
+ enforce: true
+constraints/compute.skipDefaultNetworkCreation:
+ enforce: true
iam.disableServiceAccountKeyCreation:
enforce: true
-
iam.disableServiceAccountKeyUpload:
enforce: false
rules:
- - condition:
- expression: resource.matchTagId("tagKeys/1234", "tagValues/1234")
- title: condition
- description: test condition
- location: xxx
- enforce: true
+ - condition:
+ description: test condition
+ expression: resource.matchTagId("tagKeys/1234", "tagValues/1234")
+ location: somewhere
+ title: condition
+ enforce: true
```
```yaml
-# tftest file list configs/org-policies/list.yaml
-compute.vmExternalIpAccess:
- deny:
- all: true
-
-iam.allowedPolicyMemberDomains:
+# tftest-file id=list path=configs/org-policies/list.yaml
+constraints/compute.trustedImageProjects:
allow:
values:
- - C0xxxxxxx
- - C0yyyyyyy
-
-compute.restrictLoadBalancerCreationForTypes:
+ - projects/my-project
+constraints/compute.vmExternalIpAccess:
deny:
- values: ["in:EXTERNAL"]
- rules:
- - condition:
- expression: resource.matchTagId("tagKeys/1234", "tagValues/1234")
- title: condition
- description: test condition
- allow:
- values: ["in:EXTERNAL"]
- - condition:
- expression: resource.matchTagId("tagKeys/12345", "tagValues/12345")
- title: condition2
- description: test condition2
- allow:
- all: true
+ all: true
+constraints/iam.allowedPolicyMemberDomains:
+ allow:
+ values:
+ - C0xxxxxxx
+ - C0yyyyyyy
```
-## Logging Sinks (in same project)
+## Logging Sinks
```hcl
module "gcs" {
@@ -339,49 +403,18 @@ module "project-host" {
no-gce-instances = "resource.type=gce_instance"
}
}
-# tftest modules=5 resources=14
+# tftest modules=5 resources=14 inventory=logging.yaml
```
-## Logging Sinks (in different project)
-
-When writing to destinations in a different project, set `unique_writer` to `true`.
-
-```hcl
-module "gcs" {
- source = "./fabric/modules/gcs"
- project_id = "project-1"
- name = "gcs_sink"
- force_destroy = true
-}
-
-module "project-host" {
- source = "./fabric/modules/project"
- name = "project-2"
- billing_account = "123456-123456-123456"
- parent = "folders/1234567890"
- logging_sinks = {
- warnings = {
- destination = module.gcs.id
- filter = "severity=WARNING"
- unique_writer = true
- type = "storage"
- }
- }
-}
-# tftest modules=2 resources=4
-```
-
-
## Cloud KMS encryption keys
The module offers a simple, centralized way to assign `roles/cloudkms.cryptoKeyEncrypterDecrypter` to service identities.
```hcl
module "project" {
- source = "./fabric/modules/project"
- name = "my-project"
- billing_account = "123456-123456-123456"
- prefix = "foo"
+ source = "./fabric/modules/project"
+ name = "my-project"
+ prefix = "foo"
services = [
"compute.googleapis.com",
"storage.googleapis.com"
@@ -409,8 +442,8 @@ module "org" {
organization_id = var.organization_id
tags = {
environment = {
- description = "Environment specification."
- iam = null
+ description = "Environment specification."
+ iam = null
values = {
dev = null
prod = null
@@ -438,8 +471,8 @@ One non-obvious output is `service_accounts`, which offers a simple way to disco
```hcl
module "project" {
- source = "./fabric/modules/project"
- name = "project-example"
+ source = "./fabric/modules/project"
+ name = "project-example"
services = [
"compute.googleapis.com"
]
@@ -448,7 +481,7 @@ module "project" {
output "compute_robot" {
value = module.project.service_accounts.robots.compute
}
-# tftest modules=1 resources=2
+# tftest modules=1 resources=2 inventory:outputs.yaml
```
diff --git a/modules/project/iam.tf b/modules/project/iam.tf
index 69925cc76..3ed2d2a6f 100644
--- a/modules/project/iam.tf
+++ b/modules/project/iam.tf
@@ -47,7 +47,18 @@ locals {
}
iam_additive = {
for pair in concat(local._iam_additive_pairs, local._iam_additive_member_pairs) :
- "${pair.role}-${pair.member}" => pair
+ "${pair.role}-${pair.member}" => {
+ role = pair.role
+ member = (
+ pair.member == "cloudservices"
+ ? "serviceAccount:${local.service_account_cloud_services}"
+ : pair.member == "default-compute"
+ ? "serviceAccount:${local.service_accounts_default.compute}"
+ : pair.member == "default-gae"
+ ? "serviceAccount:${local.service_accounts_default.gae}"
+ : try("serviceAccount:${local.service_accounts_robots[pair.member]}", pair.member)
+ )
+ }
}
}
diff --git a/modules/project/organization-policies.tf b/modules/project/organization-policies.tf
index 7763aff40..4ff5bb992 100644
--- a/modules/project/organization-policies.tf
+++ b/modules/project/organization-policies.tf
@@ -95,23 +95,6 @@ resource "google_org_policy_policy" "default" {
inherit_from_parent = each.value.inherit_from_parent
reset = each.value.reset
- rules {
- allow_all = try(each.value.allow.all, null) == true ? "TRUE" : null
- deny_all = try(each.value.deny.all, null) == true ? "TRUE" : null
- enforce = (
- each.value.is_boolean_policy && each.value.enforce != null
- ? upper(tostring(each.value.enforce))
- : null
- )
- dynamic "values" {
- for_each = each.value.has_values ? [1] : []
- content {
- allowed_values = try(each.value.allow.values, null)
- denied_values = try(each.value.deny.values, null)
- }
- }
- }
-
dynamic "rules" {
for_each = each.value.rules
iterator = rule
@@ -138,5 +121,22 @@ resource "google_org_policy_policy" "default" {
}
}
}
+
+ rules {
+ allow_all = try(each.value.allow.all, null) == true ? "TRUE" : null
+ deny_all = try(each.value.deny.all, null) == true ? "TRUE" : null
+ enforce = (
+ each.value.is_boolean_policy && each.value.enforce != null
+ ? upper(tostring(each.value.enforce))
+ : null
+ )
+ dynamic "values" {
+ for_each = each.value.has_values ? [1] : []
+ content {
+ allowed_values = try(each.value.allow.values, null)
+ denied_values = try(each.value.deny.values, null)
+ }
+ }
+ }
}
}
diff --git a/modules/project/service-accounts.tf b/modules/project/service-accounts.tf
index e1f6cb71a..e93978a86 100644
--- a/modules/project/service-accounts.tf
+++ b/modules/project/service-accounts.tf
@@ -45,6 +45,9 @@ locals {
# TODO: jit?
gke-mcs = "service-%s@gcp-sa-mcsd"
monitoring-notifications = "service-%s@gcp-sa-monitoring-notification"
+ multicluster-ingress = "service-%s@gcp-sa-multiclusteringress"
+ multicluster-discovery = "service-%s@gcp-sa-mcsd"
+ notebooks = "service-%s@gcp-sa-notebooks"
pubsub = "service-%s@gcp-sa-pubsub"
secretmanager = "service-%s@gcp-sa-secretmanager"
sql = "service-%s@gcp-sa-cloud-sql"
@@ -67,14 +70,19 @@ locals {
gke-mcs-importer = "${local.project.project_id}.svc.id.goog[gke-mcs/gke-mcs-importer]"
}
)
+ # JIT-ed service accounts are created without default roles granted, these needs to be assigned manually to them
+ # Roles can be found here: https://cloud.google.com/iam/docs/service-agents
+ # Remember to update "Service identities requiring manual IAM grants" in README.md when updating this list
service_accounts_jit_services = [
- "apigee.googleapis.com",
- "artifactregistry.googleapis.com",
- "cloudasset.googleapis.com",
- "gkehub.googleapis.com",
- "pubsub.googleapis.com",
- "secretmanager.googleapis.com",
- "sqladmin.googleapis.com"
+ "apigee.googleapis.com", # grant roles/apigee.serviceAgent to apigee
+ "artifactregistry.googleapis.com", # grant roles/artifactregistry.serviceAgent to artifactregistry
+ "cloudasset.googleapis.com", # grant roles/cloudasset.serviceAgent to cloudasset
+ "cloudbuild.googleapis.com", # grant roles/cloudbuild.builds.builder to cloudbuild
+ "gkehub.googleapis.com", # grant roles/gkehub.serviceAgent to fleet
+ "multiclusteringress.googleapis.com", # grant roles/multiclusteringress.serviceAgent to multicluster-ingress
+ "pubsub.googleapis.com", # grant roles/pubsub.serviceAgent to pubsub
+ "secretmanager.googleapis.com", # no grants needed
+ "sqladmin.googleapis.com", # grant roles/cloudsql.serviceAgent to sqladmin (TODO: verify)
]
service_accounts_cmek_service_keys = distinct(flatten([
for s in keys(var.service_encryption_key_ids) : [
diff --git a/modules/project/versions.tf b/modules/project/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/project/versions.tf
+++ b/modules/project/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/projects-data-source/versions.tf b/modules/projects-data-source/versions.tf
index 23f38edbc..d9c1d37c7 100644
--- a/modules/projects-data-source/versions.tf
+++ b/modules/projects-data-source/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/pubsub/README.md b/modules/pubsub/README.md
index b204ff564..81e433659 100644
--- a/modules/pubsub/README.md
+++ b/modules/pubsub/README.md
@@ -28,7 +28,7 @@ module "topic_with_schema" {
name = "my-topic"
schema = {
msg_encoding = "JSON"
- schema_type = "AVRO"
+ schema_type = "AVRO"
definition = jsonencode({
"type" = "record",
"name" = "Avro",
diff --git a/modules/pubsub/versions.tf b/modules/pubsub/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/pubsub/versions.tf
+++ b/modules/pubsub/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/secret-manager/README.md b/modules/secret-manager/README.md
index a0a55e473..6816db4d4 100644
--- a/modules/secret-manager/README.md
+++ b/modules/secret-manager/README.md
@@ -16,7 +16,7 @@ The secret replication policy is automatically managed if no location is set, or
module "secret-manager" {
source = "./fabric/modules/secret-manager"
project_id = "my-project"
- secrets = {
+ secrets = {
test-auto = null
test-manual = ["europe-west1", "europe-west4"]
}
@@ -32,12 +32,12 @@ IAM bindings can be set per secret in the same way as for most other modules sup
module "secret-manager" {
source = "./fabric/modules/secret-manager"
project_id = "my-project"
- secrets = {
+ secrets = {
test-auto = null
test-manual = ["europe-west1", "europe-west4"]
}
iam = {
- test-auto = {
+ test-auto = {
"roles/secretmanager.secretAccessor" = ["group:auto-readers@example.com"]
}
test-manual = {
@@ -56,7 +56,7 @@ As mentioned above, please be aware that **version data will be stored in state
module "secret-manager" {
source = "./fabric/modules/secret-manager"
project_id = "my-project"
- secrets = {
+ secrets = {
test-auto = null
test-manual = ["europe-west1", "europe-west4"]
}
diff --git a/modules/secret-manager/versions.tf b/modules/secret-manager/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/secret-manager/versions.tf
+++ b/modules/secret-manager/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/service-directory/README.md b/modules/service-directory/README.md
index ded837f23..d6961b418 100644
--- a/modules/service-directory/README.md
+++ b/modules/service-directory/README.md
@@ -11,10 +11,10 @@ It can be used in conjunction with the [DNS](../dns) module to create [service-d
```hcl
module "service-directory" {
- source = "./fabric/modules/service-directory"
- project_id = "my-project"
- location = "europe-west1"
- name = "sd-1"
+ source = "./fabric/modules/service-directory"
+ project_id = "my-project"
+ location = "europe-west1"
+ name = "sd-1"
iam = {
"roles/servicedirectory.editor" = [
"serviceAccount:namespace-editor@example.com"
@@ -28,10 +28,10 @@ module "service-directory" {
```hcl
module "service-directory" {
- source = "./fabric/modules/service-directory"
- project_id = "my-project"
- location = "europe-west1"
- name = "sd-1"
+ source = "./fabric/modules/service-directory"
+ project_id = "my-project"
+ location = "europe-west1"
+ name = "sd-1"
services = {
one = {
endpoints = ["first", "second"]
@@ -59,9 +59,9 @@ Wiring a service directory namespace to a private DNS zone allows querying the n
```hcl
module "service-directory" {
- source = "./fabric/modules/service-directory"
- project_id = "my-project"
- location = "europe-west1"
+ source = "./fabric/modules/service-directory"
+ project_id = "my-project"
+ location = "europe-west1"
name = "apps"
iam = {
"roles/servicedirectory.editor" = [
diff --git a/modules/service-directory/versions.tf b/modules/service-directory/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/service-directory/versions.tf
+++ b/modules/service-directory/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/source-repository/README.md b/modules/source-repository/README.md
index 9baf0ebd0..389de9e9d 100644
--- a/modules/source-repository/README.md
+++ b/modules/source-repository/README.md
@@ -27,16 +27,16 @@ module "repo" {
name = "my-repo"
triggers = {
foo = {
- filename = "ci/workflow-foo.yaml"
- included_files = ["**/*tf"]
+ filename = "ci/workflow-foo.yaml"
+ included_files = ["**/*tf"]
service_account = null
substitutions = {
BAR = 1
}
template = {
branch_name = "main"
- project_id = null
- tag_name = null
+ project_id = null
+ tag_name = null
}
}
}
diff --git a/modules/source-repository/versions.tf b/modules/source-repository/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/source-repository/versions.tf
+++ b/modules/source-repository/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/modules/vpc-sc/README.md b/modules/vpc-sc/README.md
index 8ba38c105..8e412bcfa 100644
--- a/modules/vpc-sc/README.md
+++ b/modules/vpc-sc/README.md
@@ -34,6 +34,21 @@ module "test" {
# tftest modules=1 resources=1
```
+If you need the module to create a scoped policy for you, specify 'scopes' of the policy in the `access_policy_create` variable:
+
+```hcl
+module "test" {
+ source = "./fabric/modules/vpc-sc"
+ access_policy = null
+ access_policy_create = {
+ parent = "organizations/123456"
+ title = "vpcsc-policy"
+ scopes = ["folders/456789"]
+ }
+}
+# tftest modules=1 resources=1
+```
+
### Access levels
As highlighted above, the `access_levels` type replicates the underlying resource structure.
@@ -120,7 +135,7 @@ module "test" {
to = {
operations = [{
method_selectors = ["*"]
- service_name = "storage.googleapis.com"
+ service_name = "storage.googleapis.com"
}]
resources = ["projects/123456789"]
}
@@ -189,11 +204,11 @@ module "test" {
|---|---|:---:|:---:|:---:|
| [access_policy](variables.tf#L56) | Access Policy name, set to null if creating one. | string | ✓ | |
| [access_levels](variables.tf#L17) | Access level definitions. | map(object({…})) | | {} |
-| [access_policy_create](variables.tf#L61) | Access Policy configuration, fill in to create. Parent is in 'organizations/123456' format. | object({…}) | | null |
-| [egress_policies](variables.tf#L70) | Egress policy definitions that can be referenced in perimeters. | map(object({…})) | | {} |
-| [ingress_policies](variables.tf#L99) | Ingress policy definitions that can be referenced in perimeters. | map(object({…})) | | {} |
-| [service_perimeters_bridge](variables.tf#L130) | Bridge service perimeters. | map(object({…})) | | {} |
-| [service_perimeters_regular](variables.tf#L140) | Regular service perimeters. | map(object({…})) | | {} |
+| [access_policy_create](variables.tf#L61) | Access Policy configuration, fill in to create. Parent is in 'organizations/123456' format, scopes are in 'folders/456789' or 'projects/project_id' format. | object({…}) | | null |
+| [egress_policies](variables.tf#L71) | Egress policy definitions that can be referenced in perimeters. | map(object({…})) | | {} |
+| [ingress_policies](variables.tf#L100) | Ingress policy definitions that can be referenced in perimeters. | map(object({…})) | | {} |
+| [service_perimeters_bridge](variables.tf#L131) | Bridge service perimeters. | map(object({…})) | | {} |
+| [service_perimeters_regular](variables.tf#L141) | Regular service perimeters. | map(object({…})) | | {} |
## Outputs
diff --git a/modules/vpc-sc/main.tf b/modules/vpc-sc/main.tf
index 0b06b4814..7dd589044 100644
--- a/modules/vpc-sc/main.tf
+++ b/modules/vpc-sc/main.tf
@@ -25,4 +25,5 @@ resource "google_access_context_manager_access_policy" "default" {
count = var.access_policy_create != null ? 1 : 0
parent = var.access_policy_create.parent
title = var.access_policy_create.title
+ scopes = var.access_policy_create.scopes
}
diff --git a/modules/vpc-sc/variables.tf b/modules/vpc-sc/variables.tf
index a196cc52b..a10b07689 100644
--- a/modules/vpc-sc/variables.tf
+++ b/modules/vpc-sc/variables.tf
@@ -59,10 +59,11 @@ variable "access_policy" {
}
variable "access_policy_create" {
- description = "Access Policy configuration, fill in to create. Parent is in 'organizations/123456' format."
+ description = "Access Policy configuration, fill in to create. Parent is in 'organizations/123456' format, scopes are in 'folders/456789' or 'projects/project_id' format."
type = object({
parent = string
title = string
+ scopes = optional(list(string), null)
})
default = null
}
diff --git a/modules/vpc-sc/versions.tf b/modules/vpc-sc/versions.tf
index 286536a65..08492c6f9 100644
--- a/modules/vpc-sc/versions.tf
+++ b/modules/vpc-sc/versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.40.0" # tftest
+ version = ">= 4.50.0" # tftest
}
}
}
diff --git a/tests/blueprints/cloud_operations/apigee/__init__.py b/tests/blueprints/apigee/bigquery-analytics/__init__.py
similarity index 100%
rename from tests/blueprints/cloud_operations/apigee/__init__.py
rename to tests/blueprints/apigee/bigquery-analytics/__init__.py
diff --git a/tests/blueprints/apigee/bigquery-analytics/basic.tfvars b/tests/blueprints/apigee/bigquery-analytics/basic.tfvars
new file mode 100644
index 000000000..2f9315a43
--- /dev/null
+++ b/tests/blueprints/apigee/bigquery-analytics/basic.tfvars
@@ -0,0 +1,24 @@
+project_create = {
+ billing_account_id = "12345-12345-12345"
+ parent = "folders/123456789"
+}
+project_id = "my-project"
+envgroups = {
+ test = ["test.cool-demos.space"]
+}
+environments = {
+ apis-test = {
+ envgroups = ["test"]
+ }
+}
+instances = {
+ instance-ew1 = {
+ region = "europe-west1"
+ environments = ["apis-test"]
+ runtime_ip_cidr_range = "10.0.4.0/22"
+ troubleshooting_ip_cidr_range = "10.1.0.0/28"
+ }
+}
+psc_config = {
+ europe-west1 = "10.0.0.0/28"
+}
diff --git a/tests/modules/net_glb/__init__.py b/tests/blueprints/apigee/bigquery-analytics/basic.yaml
similarity index 93%
rename from tests/modules/net_glb/__init__.py
rename to tests/blueprints/apigee/bigquery-analytics/basic.yaml
index 6d6d1266c..691af456b 100644
--- a/tests/modules/net_glb/__init__.py
+++ b/tests/blueprints/apigee/bigquery-analytics/basic.yaml
@@ -11,3 +11,7 @@
# 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.
+
+counts:
+ modules: 9
+ resources: 62
diff --git a/tests/blueprints/factories/bigquery_factory/fixture/views/view_a.yaml b/tests/blueprints/apigee/bigquery-analytics/tftest.yaml
similarity index 89%
rename from tests/blueprints/factories/bigquery_factory/fixture/views/view_a.yaml
rename to tests/blueprints/apigee/bigquery-analytics/tftest.yaml
index 23c41b98f..a3441f559 100644
--- a/tests/blueprints/factories/bigquery_factory/fixture/views/view_a.yaml
+++ b/tests/blueprints/apigee/bigquery-analytics/tftest.yaml
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-dataset: dataset_b
-view: view_a
-query: "SELECT CURRENT_DATE() LIMIT 1"
+module: blueprints/apigee/bigquery-analytics
+
+tests:
+ basic:
diff --git a/tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/__init__.py b/tests/blueprints/apigee/hybrid-gke/__init__.py
similarity index 100%
rename from tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/__init__.py
rename to tests/blueprints/apigee/hybrid-gke/__init__.py
diff --git a/tests/blueprints/apigee/hybrid-gke/basic.tfvars b/tests/blueprints/apigee/hybrid-gke/basic.tfvars
new file mode 100644
index 000000000..5b2cb4ccf
--- /dev/null
+++ b/tests/blueprints/apigee/hybrid-gke/basic.tfvars
@@ -0,0 +1,6 @@
+project_create = {
+ billing_account_id = "12345-12345-12345"
+ parent = "folders/123456789"
+}
+project_id = "my-project"
+hostname = "test.myorg.org"
\ No newline at end of file
diff --git a/tests/modules/iam_service_account/__init__.py b/tests/blueprints/apigee/hybrid-gke/basic.yaml
similarity index 93%
rename from tests/modules/iam_service_account/__init__.py
rename to tests/blueprints/apigee/hybrid-gke/basic.yaml
index 6d6d1266c..0bab56418 100644
--- a/tests/modules/iam_service_account/__init__.py
+++ b/tests/blueprints/apigee/hybrid-gke/basic.yaml
@@ -11,3 +11,7 @@
# 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.
+
+counts:
+ modules: 17
+ resources: 59
diff --git a/tests/modules/net_vpc/data/factory-subnet2.yaml b/tests/blueprints/apigee/hybrid-gke/tftest.yaml
similarity index 87%
rename from tests/modules/net_vpc/data/factory-subnet2.yaml
rename to tests/blueprints/apigee/hybrid-gke/tftest.yaml
index e110c1625..ebe16e577 100644
--- a/tests/modules/net_vpc/data/factory-subnet2.yaml
+++ b/tests/blueprints/apigee/hybrid-gke/tftest.yaml
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-region: europe-west4
-description: Sample description
-ip_cidr_range: 10.129.0.0/24
+module: blueprints/apigee/hybrid-gke
+
+tests:
+ basic:
diff --git a/tests/blueprints/factories/bigquery_factory/__init__.py b/tests/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/__init__.py
similarity index 100%
rename from tests/blueprints/factories/bigquery_factory/__init__.py
rename to tests/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/__init__.py
diff --git a/tests/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/basic.tfvars b/tests/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/basic.tfvars
new file mode 100644
index 000000000..ae07c514f
--- /dev/null
+++ b/tests/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/basic.tfvars
@@ -0,0 +1,5 @@
+billing_account_id = "12345-12345-12345"
+parent = "folders/123456789"
+apigee_project_id = "my-apigee-project"
+onprem_project_id = "my-onprem-project"
+hostname = "test.myorg.org"
diff --git a/tests/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/basic.yaml b/tests/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/basic.yaml
new file mode 100644
index 000000000..de461ff2e
--- /dev/null
+++ b/tests/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/basic.yaml
@@ -0,0 +1,17 @@
+# 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.
+
+counts:
+ modules: 13
+ resources: 73
diff --git a/tests/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/tftest.yaml b/tests/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/tftest.yaml
new file mode 100644
index 000000000..5c92fb82a
--- /dev/null
+++ b/tests/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/tftest.yaml
@@ -0,0 +1,18 @@
+# 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: blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg
+
+tests:
+ basic:
diff --git a/tests/blueprints/cloud_operations/apigee/fixture/main.tf b/tests/blueprints/cloud_operations/apigee/fixture/main.tf
deleted file mode 100644
index bb319de2d..000000000
--- a/tests/blueprints/cloud_operations/apigee/fixture/main.tf
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * 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 "test" {
- source = "../../../../../blueprints/cloud-operations/apigee"
- project_create = var.project_create
- project_id = var.project_id
- organization = var.organization
- envgroups = var.envgroups
- environments = var.environments
- instances = var.instances
- path = var.path
- datastore_name = var.datastore_name
- psc_config = var.psc_config
-}
diff --git a/tests/blueprints/cloud_operations/apigee/fixture/variables.tf b/tests/blueprints/cloud_operations/apigee/fixture/variables.tf
deleted file mode 100644
index d66f3874d..000000000
--- a/tests/blueprints/cloud_operations/apigee/fixture/variables.tf
+++ /dev/null
@@ -1,123 +0,0 @@
-/**
- * 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.
- */
-
-/**
- * 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 "project_create" {
- description = "Parameters for the creation of the new project."
- type = object({
- billing_account_id = string
- parent = string
- })
- default = null
-}
-
-variable "vpc_create" {
- description = "Boolean flag indicating whether the VPC should be created or not."
- type = bool
- default = true
-}
-
-variable "project_id" {
- description = "Project ID."
- type = string
- nullable = false
-}
-
-variable "organization" {
- description = "Apigee organization."
- type = object({
- display_name = optional(string, "Apigee organization created by tf module")
- description = optional(string, "Apigee organization created by tf module")
- authorized_network = optional(string, "vpc")
- runtime_type = optional(string, "CLOUD")
- billing_type = optional(string)
- database_encryption_key = optional(string)
- analytics_region = optional(string, "europe-west1")
- })
- nullable = false
- default = {
- }
-}
-
-variable "envgroups" {
- description = "Environment groups (NAME => [HOSTNAMES])."
- type = map(list(string))
- nullable = false
-}
-
-variable "environments" {
- description = "Environments."
- type = map(object({
- display_name = optional(string)
- description = optional(string)
- node_config = optional(object({
- min_node_count = optional(number)
- max_node_count = optional(number)
- }))
- iam = optional(map(list(string)))
- envgroups = list(string)
- }))
- nullable = false
-}
-
-variable "instances" {
- description = "Instance."
- type = map(object({
- display_name = optional(string)
- description = optional(string)
- region = string
- environments = list(string)
- psa_ip_cidr_range = string
- disk_encryption_key = optional(string)
- consumer_accept_list = optional(list(string))
- }))
- nullable = false
-}
-
-variable "path" {
- description = "Bucket path."
- type = string
- default = "/analytics"
- nullable = false
-}
-
-variable "datastore_name" {
- description = "Datastore"
- type = string
- nullable = false
- default = "gcs"
-}
-
-variable "psc_config" {
- description = "PSC configuration."
- type = map(string)
- nullable = false
-}
diff --git a/tests/blueprints/cloud_operations/asset_inventory_feed_remediation/test_plan.py b/tests/blueprints/cloud_operations/asset_inventory_feed_remediation/test_plan.py
index df03e144f..497af6be5 100644
--- a/tests/blueprints/cloud_operations/asset_inventory_feed_remediation/test_plan.py
+++ b/tests/blueprints/cloud_operations/asset_inventory_feed_remediation/test_plan.py
@@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner()
assert len(modules) == 6
- assert len(resources) == 18
+ assert len(resources) == 19
diff --git a/tests/blueprints/cloud_operations/scheduled_asset_inventory_export_bq/test_plan.py b/tests/blueprints/cloud_operations/scheduled_asset_inventory_export_bq/test_plan.py
index c5394839d..3bcc63440 100644
--- a/tests/blueprints/cloud_operations/scheduled_asset_inventory_export_bq/test_plan.py
+++ b/tests/blueprints/cloud_operations/scheduled_asset_inventory_export_bq/test_plan.py
@@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner()
assert len(modules) == 7
- assert len(resources) == 29
+ assert len(resources) == 30
diff --git a/tests/fast/stages/s00_bootstrap/__init__.py b/tests/blueprints/cloud_operations/terraform_enterprise_wif/__init__.py
similarity index 100%
rename from tests/fast/stages/s00_bootstrap/__init__.py
rename to tests/blueprints/cloud_operations/terraform_enterprise_wif/__init__.py
diff --git a/tests/fast/stages/s01_resman/__init__.py b/tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/__init__.py
similarity index 100%
rename from tests/fast/stages/s01_resman/__init__.py
rename to tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/__init__.py
diff --git a/tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/fixture/main.tf b/tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/fixture/main.tf
similarity index 100%
rename from tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/fixture/main.tf
rename to tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/fixture/main.tf
diff --git a/tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/fixture/variables.tf b/tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/fixture/variables.tf
similarity index 100%
rename from tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/fixture/variables.tf
rename to tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/fixture/variables.tf
diff --git a/tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/test_plan.py b/tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/test_plan.py
similarity index 100%
rename from tests/blueprints/cloud_operations/terraform-enterprise-wif/gcp-workload-identity-provider/test_plan.py
rename to tests/blueprints/cloud_operations/terraform_enterprise_wif/gcp_workload_identity_provider/test_plan.py
diff --git a/tests/blueprints/cloud_operations/unmanaged_instances_healthcheck/test_plan.py b/tests/blueprints/cloud_operations/unmanaged_instances_healthcheck/test_plan.py
index 3f6f34994..b1f0fba3c 100644
--- a/tests/blueprints/cloud_operations/unmanaged_instances_healthcheck/test_plan.py
+++ b/tests/blueprints/cloud_operations/unmanaged_instances_healthcheck/test_plan.py
@@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner()
assert len(modules) == 10
- assert len(resources) == 34
+ assert len(resources) == 32
diff --git a/tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/main.tf b/tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/main.tf
index 65cc20aeb..3fee8af5f 100644
--- a/tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/main.tf
+++ b/tests/blueprints/data_solutions/cmek_via_centralized_kms/fixture/main.tf
@@ -15,7 +15,10 @@
*/
module "test" {
- source = "../../../../../blueprints/data-solutions/cmek-via-centralized-kms/"
- billing_account = var.billing_account
- root_node = var.root_node
+ source = "../../../../../blueprints/data-solutions/cmek-via-centralized-kms/"
+ project_config = {
+ billing_account_id = "123456-123456-123456"
+ parent = "folders/12345678"
+ }
+ prefix = "prefix"
}
diff --git a/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py b/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py
index 1b51472cd..785f47053 100644
--- a/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py
+++ b/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py
@@ -21,5 +21,6 @@ FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
def test_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner(FIXTURES_DIR)
- assert len(modules) == 41
- assert len(resources) == 315
+
+ assert len(modules) == 42
+ assert len(resources) == 296
diff --git a/tests/blueprints/data_solutions/data_playground/test_plan.py b/tests/blueprints/data_solutions/data_playground/test_plan.py
index a0c3b5e6f..daaa57fc9 100644
--- a/tests/blueprints/data_solutions/data_playground/test_plan.py
+++ b/tests/blueprints/data_solutions/data_playground/test_plan.py
@@ -22,4 +22,4 @@ def test_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner(FIXTURES_DIR)
assert len(modules) == 7
- assert len(resources) == 37
+ assert len(resources) == 38
diff --git a/tests/blueprints/data_solutions/shielded_folder/simple.tfvars b/tests/blueprints/data_solutions/shielded_folder/simple.tfvars
new file mode 100644
index 000000000..83e8b1399
--- /dev/null
+++ b/tests/blueprints/data_solutions/shielded_folder/simple.tfvars
@@ -0,0 +1,20 @@
+access_policy_config = {
+ access_policy_create = {
+ parent = "organizations/1234567890123"
+ title = "ShieldedMVP"
+ }
+}
+folder_config = {
+ folder_create = {
+ display_name = "ShieldedMVP"
+ parent = "organizations/1234567890123"
+ }
+}
+organization = {
+ domain = "example.com"
+ id = "1122334455"
+}
+prefix = "prefix"
+project_config = {
+ billing_account_id = "123456-123456-123456"
+}
diff --git a/tests/blueprints/data_solutions/shielded_folder/simple.yaml b/tests/blueprints/data_solutions/shielded_folder/simple.yaml
new file mode 100644
index 000000000..244dcb976
--- /dev/null
+++ b/tests/blueprints/data_solutions/shielded_folder/simple.yaml
@@ -0,0 +1,51 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.folder.google_compute_firewall_policy.policy["prefix-fw-policy"]:
+ short_name: prefix-fw-policy
+ module.folder.google_folder.folder[0]:
+ display_name: ShieldedMVP
+ parent: organizations/1234567890123
+ module.log-export-project[0].google_project.project[0]:
+ billing_account: 123456-123456-123456
+ project_id: prefix-audit-logs
+ module.vpc-sc[0].google_access_context_manager_access_policy.default[0]:
+ parent: organizations/1122334455
+ title: shielded-folder
+ module.vpc-sc[0].google_access_context_manager_service_perimeter.regular["shielded"]:
+ description: null
+ perimeter_type: PERIMETER_TYPE_REGULAR
+ title: shielded
+
+counts:
+ google_access_context_manager_access_policy: 1
+ google_access_context_manager_service_perimeter: 1
+ google_bigquery_dataset: 1
+ google_bigquery_dataset_iam_member: 2
+ google_bigquery_default_service_account: 1
+ google_compute_firewall_policy: 1
+ google_compute_firewall_policy_rule: 4
+ google_folder: 2
+ google_folder_iam_binding: 2
+ google_logging_folder_sink: 2
+ google_org_policy_policy: 12
+ google_project: 1
+ google_project_iam_binding: 1
+ google_project_service: 4
+ google_project_service_identity: 1
+ google_projects: 1
+ google_storage_project_service_account: 1
+ modules: 5
+ resources: 38
diff --git a/tests/blueprints/data_solutions/shielded_folder/tftest.yaml b/tests/blueprints/data_solutions/shielded_folder/tftest.yaml
new file mode 100644
index 000000000..3c0bcb8c4
--- /dev/null
+++ b/tests/blueprints/data_solutions/shielded_folder/tftest.yaml
@@ -0,0 +1,18 @@
+# Copyright 2023 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: blueprints/data-solutions/shielded-folder
+
+tests:
+ simple:
diff --git a/tests/fast/stages/s02_networking_nva/__init__.py b/tests/blueprints/data_solutions/vertex_mlops/__init__.py
similarity index 100%
rename from tests/fast/stages/s02_networking_nva/__init__.py
rename to tests/blueprints/data_solutions/vertex_mlops/__init__.py
diff --git a/tests/fast/stages/s03_project_factory/fixture/main.tf b/tests/blueprints/data_solutions/vertex_mlops/fixture/main.tf
similarity index 52%
rename from tests/fast/stages/s03_project_factory/fixture/main.tf
rename to tests/blueprints/data_solutions/vertex_mlops/fixture/main.tf
index 420cc2567..0b671f335 100644
--- a/tests/fast/stages/s03_project_factory/fixture/main.tf
+++ b/tests/blueprints/data_solutions/vertex_mlops/fixture/main.tf
@@ -15,18 +15,25 @@
*/
module "projects" {
- source = "../../../../../fast/stages/03-project-factory/dev"
- data_dir = "./data/projects/"
- defaults_file = "./data/defaults.yaml"
- prefix = "test"
- environment_dns_zone = "dev"
- billing_account = {
- id = "000000-111111-222222"
- organization_id = 123456789012
+ source = "../../../../../blueprints/data-solutions/vertex-mlops/"
+ labels = {
+ "env" : "dev",
+ "team" : "ml"
}
- vpc_self_links = {
- dev-spoke-0 = "link"
+ bucket_name = "test-dev"
+ dataset_name = "test"
+ identity_pool_claims = "attribute.repository/ORGANIZATION/REPO"
+ notebooks = {
+ "myworkbench" : {
+ "owner" : "user@example.com",
+ "region" : "europe-west4",
+ "subnet" : "default",
+ }
+ }
+ prefix = "pref"
+ project_id = "test-dev"
+ project_create = {
+ billing_account_id = "000000-123456-123456"
+ parent = "folders/111111111111"
}
}
-
-
diff --git a/tests/blueprints/data_solutions/vertex_mlops/test_plan.py b/tests/blueprints/data_solutions/vertex_mlops/test_plan.py
new file mode 100644
index 000000000..eac30ad57
--- /dev/null
+++ b/tests/blueprints/data_solutions/vertex_mlops/test_plan.py
@@ -0,0 +1,23 @@
+# 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.
+import os
+import pytest
+
+FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
+
+def test_resources(e2e_plan_runner):
+ "Test that plan works and the numbers of resources is as expected."
+ modules, resources = e2e_plan_runner(FIXTURES_DIR)
+ # TODO: to re-enable per-module resource count check print _, then test
+ assert len(modules) > 0 and len(resources) > 0
\ No newline at end of file
diff --git a/tests/blueprints/factories/bigquery_factory/examples/simple.yaml b/tests/blueprints/factories/bigquery_factory/examples/simple.yaml
new file mode 100644
index 000000000..d32492d6c
--- /dev/null
+++ b/tests/blueprints/factories/bigquery_factory/examples/simple.yaml
@@ -0,0 +1,40 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.bq.module.bq["my_dataset"].google_bigquery_dataset.default:
+ dataset_id: my_dataset
+ project: project-id
+ module.bq.module.bq["my_dataset"].google_bigquery_table.default["countries"]:
+ dataset_id: my_dataset
+ friendly_name: countries
+ labels:
+ env: prod
+ project: project-id
+ schema: '[{"name":"country","type":"STRING"},{"name":"population","type":"INT64"}]'
+ table_id: countries
+ module.bq.module.bq["my_dataset"].google_bigquery_table.views["department"]:
+ dataset_id: my_dataset
+ friendly_name: department
+ labels:
+ env: prod
+ project: project-id
+ table_id: department
+ view:
+ - query: SELECT SUM(population) from my_dataset.countries
+ use_legacy_sql: false
+
+counts:
+ google_bigquery_dataset: 1
+ google_bigquery_table: 2
diff --git a/tests/blueprints/factories/bigquery_factory/fixture/variables.tf b/tests/blueprints/factories/bigquery_factory/fixture/variables.tf
deleted file mode 100644
index 8269dbbe1..000000000
--- a/tests/blueprints/factories/bigquery_factory/fixture/variables.tf
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * 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 "views_dir" {
- description = "Relative path for the folder storing view data."
- type = string
- default = "/views"
-}
-
-variable "tables_dir" {
- description = "Relative path for the folder storing table data."
- type = string
- default = "tables"
-}
-
-variable "project_id" {
- description = "Project ID"
- type = string
- default = "test-project"
-
-}
diff --git a/tests/blueprints/gke/binauthz/test_plan.py b/tests/blueprints/gke/binauthz/test_plan.py
index cf012c061..b4437b6f3 100644
--- a/tests/blueprints/gke/binauthz/test_plan.py
+++ b/tests/blueprints/gke/binauthz/test_plan.py
@@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner()
assert len(modules) == 13
- assert len(resources) == 43
+ assert len(resources) == 44
diff --git a/tests/blueprints/gke/multi_cluster_mesh_gke_fleet_api/test_plan.py b/tests/blueprints/gke/multi_cluster_mesh_gke_fleet_api/test_plan.py
index 270a142d1..2379849dc 100644
--- a/tests/blueprints/gke/multi_cluster_mesh_gke_fleet_api/test_plan.py
+++ b/tests/blueprints/gke/multi_cluster_mesh_gke_fleet_api/test_plan.py
@@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner()
assert len(modules) == 12
- assert len(resources) == 53
+ assert len(resources) == 55
diff --git a/tests/blueprints/gke/multitenant_fleet/test_plan.py b/tests/blueprints/gke/multitenant_fleet/test_plan.py
index 2b94b766f..c8a836949 100644
--- a/tests/blueprints/gke/multitenant_fleet/test_plan.py
+++ b/tests/blueprints/gke/multitenant_fleet/test_plan.py
@@ -17,4 +17,4 @@ def test_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner()
assert len(modules) == 4
- assert len(resources) == 22
+ assert len(resources) == 23
diff --git a/tests/blueprints/networking/onprem_google_access_dns/test_plan.py b/tests/blueprints/networking/onprem_google_access_dns/test_plan.py.disabled
similarity index 100%
rename from tests/blueprints/networking/onprem_google_access_dns/test_plan.py
rename to tests/blueprints/networking/onprem_google_access_dns/test_plan.py.disabled
diff --git a/tests/blueprints/networking/private_cloud_function_from_onprem/test_plan.py b/tests/blueprints/networking/private_cloud_function_from_onprem/test_plan.py
index 2b3f8d7f7..81225db36 100644
--- a/tests/blueprints/networking/private_cloud_function_from_onprem/test_plan.py
+++ b/tests/blueprints/networking/private_cloud_function_from_onprem/test_plan.py
@@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner()
assert len(modules) == 10
- assert len(resources) == 38
+ assert len(resources) == 39
diff --git a/tests/blueprints/serverless/api_gateway/test_plan.py b/tests/blueprints/serverless/api_gateway/test_plan.py
index 6cf48a87b..9d658398e 100644
--- a/tests/blueprints/serverless/api_gateway/test_plan.py
+++ b/tests/blueprints/serverless/api_gateway/test_plan.py
@@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner()
assert len(modules) == 7
- assert len(resources) == 31
+ assert len(resources) == 32
diff --git a/tests/examples/conftest.py b/tests/examples/conftest.py
index b2b915ea1..4d3d85ee6 100644
--- a/tests/examples/conftest.py
+++ b/tests/examples/conftest.py
@@ -1,4 +1,4 @@
-# Copyright 2022 Google LLC
+# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -11,50 +11,50 @@
# 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.
+"""Pytest configuration for testing code examples."""
import collections
import re
from pathlib import Path
import marko
-import pytest
FABRIC_ROOT = Path(__file__).parents[2]
-MODULES_PATH = FABRIC_ROOT / 'modules/'
-BLUEPRINTS_PATH = FABRIC_ROOT / 'blueprints/'
-FILE_TEST_RE = re.compile(r'# tftest file (\w+) ([\S]+)')
+FILE_TEST_RE = re.compile(r'# tftest-file +id=([\w_.-]+) +path=([\S]+)')
-Example = collections.namedtuple('Example', 'code files')
+Example = collections.namedtuple('Example', 'name code module files')
File = collections.namedtuple('File', 'path content')
def pytest_generate_tests(metafunc):
+ """Find all README.md files and collect code examples tagged for testing."""
if 'example' in metafunc.fixturenames:
- modules = [x for x in MODULES_PATH.iterdir() if x.is_dir()]
- modules.extend(x for x in BLUEPRINTS_PATH.glob("*/*") if x.is_dir())
- modules.sort()
+ readmes = FABRIC_ROOT.glob('**/README.md')
examples = []
ids = []
- for module in modules:
- readme = module / 'README.md'
- if not readme.exists():
- continue
+ for readme in readmes:
+ module = readme.parent
doc = marko.parse(readme.read_text())
index = 0
- last_header = None
- files = {}
+ files = collections.defaultdict(dict)
- #first pass: collect all tftest tagged files
+ # first pass: collect all examples tagged with tftest-file
+ last_header = None
for child in doc.children:
if isinstance(child, marko.block.FencedCode):
code = child.children[0].children
match = FILE_TEST_RE.search(code)
if match:
name, path = match.groups()
- files[name] = File(path, code)
+ files[last_header][name] = File(path, code)
+ elif isinstance(child, marko.block.Heading):
+ last_header = child.children[0].children
+ # second pass: collect all examples tagged with tftest
+ last_header = None
+ index = 0
for child in doc.children:
if isinstance(child, marko.block.FencedCode):
index += 1
@@ -62,12 +62,12 @@ def pytest_generate_tests(metafunc):
if 'tftest skip' in code:
continue
if child.lang == 'hcl':
- examples.append(Example(code, files))
path = module.relative_to(FABRIC_ROOT)
name = f'{path}:{last_header}'
if index > 1:
name += f' {index}'
ids.append(name)
+ examples.append(Example(name, code, path, files[last_header]))
elif isinstance(child, marko.block.Heading):
last_header = child.children[0].children
index = 0
diff --git a/tests/examples/test_plan.py b/tests/examples/test_plan.py
index 2bb45af12..261276f73 100644
--- a/tests/examples/test_plan.py
+++ b/tests/examples/test_plan.py
@@ -1,4 +1,4 @@
-# Copyright 2022 Google LLC
+# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,19 +13,24 @@
# limitations under the License.
import re
+import subprocess
from pathlib import Path
BASE_PATH = Path(__file__).parent
-COUNT_TEST_RE = re.compile(
- r'# tftest modules=(\d+) resources=(\d+)(?: files=([\w,]+))?')
+COUNT_TEST_RE = re.compile(r'# tftest +modules=(\d+) +resources=(\d+)' +
+ r'(?: +files=([\w,_-]+))?' +
+ r'(?: +inventory=([\w\-.]+))?')
-def test_example(recursive_e2e_plan_runner, tmp_path, example):
+def test_example(plan_validator, tmp_path, example):
if match := COUNT_TEST_RE.search(example.code):
- (tmp_path / 'fabric').symlink_to(Path(BASE_PATH, '../../'))
- (tmp_path / 'variables.tf').symlink_to(Path(BASE_PATH, 'variables.tf'))
+ (tmp_path / 'fabric').symlink_to(BASE_PATH.parents[1])
+ (tmp_path / 'variables.tf').symlink_to(BASE_PATH / 'variables.tf')
(tmp_path / 'main.tf').write_text(example.code)
+ expected_modules = int(match.group(1))
+ expected_resources = int(match.group(2))
+
if match.group(3) is not None:
requested_files = match.group(3).split(',')
for f in requested_files:
@@ -33,13 +38,33 @@ def test_example(recursive_e2e_plan_runner, tmp_path, example):
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(example.files[f].content)
- expected_modules = int(match.group(1)) if match is not None else 1
- expected_resources = int(match.group(2)) if match is not None else 1
+ inventory = []
+ if match.group(4) is not None:
+ python_test_path = str(example.module).replace('-', '_')
+ inventory = BASE_PATH.parent / python_test_path / 'examples'
+ inventory = inventory / match.group(4)
- num_modules, num_resources = recursive_e2e_plan_runner(
- str(tmp_path), tmpdir=False)
+ # TODO: force plan_validator to never copy files (we're already
+ # running from a temp dir)
+ summary = plan_validator(module_path=tmp_path, inventory_paths=inventory,
+ tf_var_files=[])
+
+ import yaml
+ print(yaml.dump({"values": summary.values}))
+ print(yaml.dump({"counts": summary.counts}))
+ print(yaml.dump({"outputs": summary.outputs}))
+
+ counts = summary.counts
+ num_modules, num_resources = counts['modules'], counts['resources']
assert expected_modules == num_modules, 'wrong number of modules'
assert expected_resources == num_resources, 'wrong number of resources'
+ # TODO(jccb): this should probably be done in check_documentation
+ # but we already have all the data here.
+ result = subprocess.run(
+ 'terraform fmt -check -diff -no-color main.tf'.split(), cwd=tmp_path,
+ stdout=subprocess.PIPE, encoding='utf-8')
+ assert result.returncode == 0, f'terraform code not formatted correctly\n{result.stdout}'
+
else:
assert False, "can't find tftest directive"
diff --git a/tests/examples/variables.tf b/tests/examples/variables.tf
index 76c3770de..3a5a3f758 100644
--- a/tests/examples/variables.tf
+++ b/tests/examples/variables.tf
@@ -19,7 +19,7 @@ variable "bucket" {
}
variable "billing_account_id" {
- default = "billing_account_id"
+ default = "123456-123456-123456"
}
variable "kms_key" {
diff --git a/tests/fast/stages/s01_resman/fixture/main.tf b/tests/fast/stages/s01_resman/fixture/main.tf
deleted file mode 100644
index 57d35b163..000000000
--- a/tests/fast/stages/s01_resman/fixture/main.tf
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * 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/01-resman"
- automation = {
- federated_identity_pool = null
- federated_identity_providers = null
- project_id = "fast-prod-automation"
- project_number = 123456
- outputs_bucket = "test"
- }
- billing_account = {
- id = "000000-111111-222222"
- organization_id = 123456789012
- }
- custom_roles = {
- # organization_iam_admin = "organizations/123456789012/roles/organizationIamAdmin",
- service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin"
- }
- groups = {
- gcp-billing-admins = "gcp-billing-admins",
- gcp-devops = "gcp-devops",
- gcp-network-admins = "gcp-network-admins",
- gcp-organization-admins = "gcp-organization-admins",
- gcp-security-admins = "gcp-security-admins",
- gcp-support = "gcp-support"
- }
- organization = {
- domain = "fast.example.com"
- id = 123456789012
- customer_id = "C00000000"
- }
- prefix = "fast2"
-}
diff --git a/tests/fast/stages/s02_networking_nva/fixture/main.tf b/tests/fast/stages/s02_networking_nva/fixture/main.tf
deleted file mode 100644
index 2fc748014..000000000
--- a/tests/fast/stages/s02_networking_nva/fixture/main.tf
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * 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-nva"
- data_dir = "../../../../../fast/stages/02-networking-nva/data/"
- automation = {
- outputs_bucket = "test"
- }
- 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"
- gke-dev = "string"
- gke-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/fixture/main.tf b/tests/fast/stages/s02_networking_peering/fixture/main.tf
deleted file mode 100644
index a2ed52474..000000000
--- a/tests/fast/stages/s02_networking_peering/fixture/main.tf
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * 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/"
- automation = {
- outputs_bucket = "test"
- }
- 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
- }
- region_trigram = {
- europe-west1 = "ew1"
- europe-west3 = "ew3"
- europe-west8 = "ew8"
- }
- service_accounts = {
- data-platform-dev = "string"
- data-platform-prod = "string"
- gke-dev = "string"
- gke-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_separate_envs/fixture/main.tf b/tests/fast/stages/s02_networking_separate_envs/fixture/main.tf
deleted file mode 100644
index 9ecef1ac0..000000000
--- a/tests/fast/stages/s02_networking_separate_envs/fixture/main.tf
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * 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-separate-envs"
- data_dir = "../../../../../fast/stages/02-networking-separate-envs/data/"
- automation = {
- outputs_bucket = "test"
- }
- 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_security/fixture/main.tf b/tests/fast/stages/s02_security/fixture/main.tf
deleted file mode 100644
index d65805f9e..000000000
--- a/tests/fast/stages/s02_security/fixture/main.tf
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * 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-security"
- automation = {
- outputs_bucket = "test"
- }
- billing_account = {
- id = "000000-111111-222222"
- organization_id = 123456789012
- }
- folder_ids = {
- security = null
- }
- organization = {
- domain = "gcp-pso-italy.net"
- id = 856933387836
- customer_id = "C01lmug8b"
- }
- prefix = "fast"
- kms_keys = {
- compute = {
- iam = {
- "roles/cloudkms.admin" = ["user:user1@example.com"]
- }
- labels = { service = "compute" }
- locations = null
- rotation_period = null
- }
- }
- service_accounts = {
- security = "foobar@iam.gserviceaccount.com"
- data-platform-dev = "foobar@iam.gserviceaccount.com"
- data-platform-prod = "foobar@iam.gserviceaccount.com"
- project-factory-dev = "foobar@iam.gserviceaccount.com"
- project-factory-prod = "foobar@iam.gserviceaccount.com"
- }
- vpc_sc_access_levels = {
- onprem = {
- conditions = [{
- ip_subnetworks = ["101.101.101.0/24"]
- }]
- }
- }
- vpc_sc_egress_policies = {
- iac-gcs = {
- from = {
- identities = [
- "serviceAccount:xxx-prod-resman-security-0@xxx-prod-iac-core-0.iam.gserviceaccount.com"
- ]
- }
- to = {
- operations = [{
- method_selectors = ["*"]
- service_name = "storage.googleapis.com"
- }]
- resources = ["projects/123456782"]
- }
- }
- }
- vpc_sc_ingress_policies = {
- iac = {
- from = {
- identities = [
- "serviceAccount:xxx-prod-resman-security-0@xxx-prod-iac-core-0.iam.gserviceaccount.com"
- ]
- access_levels = ["*"]
- }
- to = {
- operations = [{ method_selectors = [], service_name = "*" }]
- resources = ["*"]
- }
- }
- }
- vpc_sc_perimeters = {
- dev = {
- egress_policies = ["iac-gcs"]
- ingress_policies = ["iac"]
- resources = ["projects/1111111111"]
- }
- dev = {
- egress_policies = ["iac-gcs"]
- ingress_policies = ["iac"]
- resources = ["projects/0000000000"]
- }
- dev = {
- access_levels = ["onprem"]
- egress_policies = ["iac-gcs"]
- ingress_policies = ["iac"]
- resources = ["projects/2222222222"]
- }
- }
-}
diff --git a/tests/fast/stages/s02_security/test_plan.py b/tests/fast/stages/s02_security/test_plan.py
deleted file mode 100644
index 5f1058065..000000000
--- a/tests/fast/stages/s02_security/test_plan.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# 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(recursive_e2e_plan_runner):
- "Test stage."
- num_modules, num_resources = recursive_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/s03_data_platform/fixture/main.tf b/tests/fast/stages/s03_data_platform/fixture/main.tf
deleted file mode 100644
index 0e356d222..000000000
--- a/tests/fast/stages/s03_data_platform/fixture/main.tf
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * 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: Data platform stage test
-
-module "stage" {
- source = "../../../../../fast/stages/03-data-platform/dev/"
- automation = {
- outputs_bucket = "test"
- }
- billing_account = {
- id = "012345-67890A-BCDEF0",
- organization_id = 123456
- }
- folder_ids = {
- data-platform-dev = "folders/12345678"
- }
- host_project_ids = {
- dev-spoke-0 = "fast-dev-net-spoke-0"
- }
- organization = {
- domain = "example.com"
- id = 123456789012
- customer_id = "A11aaaaa1"
- }
- prefix = "fast"
- subnet_self_links = {
- dev-spoke-0 = {
- "europe-west1/dev-dataplatform-ew1" : "https://www.googleapis.com/compute/v1/projects/fast-dev-net-spoke-0/regions/europe-west1/subnetworks/dev-dataplatform-ew1",
- "europe-west1/dev-default-ew1" : "https://www.googleapis.com/compute/v1/projects/fast-dev-net-spoke-0/regions/europe-west1/subnetworks/dev-default-ew1"
- }
- }
- vpc_self_links = { dev-spoke-0 = "https://www.googleapis.com/compute/v1/projects/fast-dev-net-spoke-0/global/networks/dev-spoke-0" }
-}
diff --git a/tests/fast/stages/s03_data_platform/test_plan.py b/tests/fast/stages/s03_data_platform/test_plan.py
deleted file mode 100644
index 5f1058065..000000000
--- a/tests/fast/stages/s03_data_platform/test_plan.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# 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(recursive_e2e_plan_runner):
- "Test stage."
- num_modules, num_resources = recursive_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/s03_gke_multitenant/fixture/main.tf b/tests/fast/stages/s03_gke_multitenant/fixture/main.tf
deleted file mode 100644
index b69951bad..000000000
--- a/tests/fast/stages/s03_gke_multitenant/fixture/main.tf
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * 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: Data platform stage test
-
-module "stage" {
- source = "../../../../../fast/stages/03-gke-multitenant/dev/"
- automation = {
- outputs_bucket = "test"
- }
- billing_account = {
- id = "012345-67890A-BCDEF0",
- organization_id = 123456
- }
- clusters = {
- mycluster = {
- cluster_autoscaling = null
- description = "my cluster"
- dns_domain = null
- location = "europe-west1"
- labels = {}
- private_cluster_config = {
- enable_private_endpoint = true
- master_global_access = true
- }
- vpc_config = {
- subnetwork = "projects/prj-host/regions/europe-west1/subnetworks/gke-0"
- master_ipv4_cidr_block = "172.16.20.0/28"
- }
- }
- }
- nodepools = {
- mycluster = {
- mynodepool = {
- node_count = { initial = 1 }
- }
- }
- }
- folder_ids = {
- gke-dev = "folders/12345678"
- }
- host_project_ids = {
- dev-spoke-0 = "fast-dev-net-spoke-0"
- }
- prefix = "fast"
- vpc_self_links = {
- dev-spoke-0 = "projects/fast-dev-net-spoke-0/global/networks/dev-spoke-0"
- }
-}
diff --git a/tests/fast/stages/s03_gke_multitenant/test_plan.py b/tests/fast/stages/s03_gke_multitenant/test_plan.py
deleted file mode 100644
index 5f1058065..000000000
--- a/tests/fast/stages/s03_gke_multitenant/test_plan.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# 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(recursive_e2e_plan_runner):
- "Test stage."
- num_modules, num_resources = recursive_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/s03_project_factory/test_plan.py b/tests/fast/stages/s03_project_factory/test_plan.py
deleted file mode 100644
index 5f1058065..000000000
--- a/tests/fast/stages/s03_project_factory/test_plan.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# 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(recursive_e2e_plan_runner):
- "Test stage."
- num_modules, num_resources = recursive_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_peering/__init__.py b/tests/fast/stages/s0_bootstrap/__init__.py
similarity index 100%
rename from tests/fast/stages/s02_networking_peering/__init__.py
rename to tests/fast/stages/s0_bootstrap/__init__.py
diff --git a/tests/fast/stages/s00_bootstrap/simple.tfvars b/tests/fast/stages/s0_bootstrap/simple.tfvars
similarity index 71%
rename from tests/fast/stages/s00_bootstrap/simple.tfvars
rename to tests/fast/stages/s0_bootstrap/simple.tfvars
index f8ef5735b..5c389f53a 100644
--- a/tests/fast/stages/s00_bootstrap/simple.tfvars
+++ b/tests/fast/stages/s0_bootstrap/simple.tfvars
@@ -4,8 +4,7 @@ organization = {
customer_id = "C00000000"
}
billing_account = {
- id = "000000-111111-222222"
- organization_id = 123456789012
+ id = "000000-111111-222222"
}
prefix = "fast"
outputs_location = "/fast-config"
diff --git a/tests/fast/stages/s00_bootstrap/simple.yaml b/tests/fast/stages/s0_bootstrap/simple.yaml
similarity index 77%
rename from tests/fast/stages/s00_bootstrap/simple.yaml
rename to tests/fast/stages/s0_bootstrap/simple.yaml
index 703b84b45..63a64cb9e 100644
--- a/tests/fast/stages/s00_bootstrap/simple.yaml
+++ b/tests/fast/stages/s0_bootstrap/simple.yaml
@@ -13,23 +13,22 @@
# limitations under the License.
counts:
- google_bigquery_dataset: 2
- google_bigquery_dataset_iam_member: 2
+ google_bigquery_dataset: 1
google_bigquery_default_service_account: 3
google_logging_organization_sink: 2
google_organization_iam_binding: 19
- google_organization_iam_custom_role: 2
+ google_organization_iam_custom_role: 3
google_organization_iam_member: 16
google_project: 3
google_project_iam_binding: 9
- google_project_iam_member: 1
+ google_project_iam_member: 3
google_project_service: 29
- google_project_service_identity: 2
- google_service_account: 3
- google_service_account_iam_binding: 3
- google_storage_bucket: 4
- google_storage_bucket_iam_binding: 2
- google_storage_bucket_iam_member: 3
+ google_project_service_identity: 3
+ google_service_account: 2
+ google_service_account_iam_binding: 1
+ google_storage_bucket: 3
+ google_storage_bucket_iam_binding: 1
+ google_storage_bucket_iam_member: 2
google_storage_bucket_object: 5
google_storage_project_service_account: 3
local_file: 5
@@ -38,6 +37,7 @@ outputs:
custom_roles:
organization_iam_admin: organizations/123456789012/roles/organizationIamAdmin
service_project_network_admin: organizations/123456789012/roles/serviceProjectNetworkAdmin
+ tenant_network_admin: organizations/123456789012/roles/tenantNetworkAdmin
outputs_bucket: fast-prod-iac-core-outputs-0
project_ids:
automation: fast-prod-iac-core-0
@@ -45,5 +45,4 @@ outputs:
log-export: fast-prod-audit-logs-0
service_accounts:
bootstrap: fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com
- cicd: fast-prod-cicd-0@fast-prod-iac-core-0.iam.gserviceaccount.com
resman: fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com
diff --git a/tests/fast/stages/s00_bootstrap/simple_projects.yaml b/tests/fast/stages/s0_bootstrap/simple_projects.yaml
similarity index 100%
rename from tests/fast/stages/s00_bootstrap/simple_projects.yaml
rename to tests/fast/stages/s0_bootstrap/simple_projects.yaml
diff --git a/tests/fast/stages/s00_bootstrap/simple_sas.yaml b/tests/fast/stages/s0_bootstrap/simple_sas.yaml
similarity index 82%
rename from tests/fast/stages/s00_bootstrap/simple_sas.yaml
rename to tests/fast/stages/s0_bootstrap/simple_sas.yaml
index ba84948d8..0424e5983 100644
--- a/tests/fast/stages/s00_bootstrap/simple_sas.yaml
+++ b/tests/fast/stages/s0_bootstrap/simple_sas.yaml
@@ -17,10 +17,6 @@ values:
account_id: fast-prod-bootstrap-0
display_name: Terraform organization bootstrap service account.
project: fast-prod-iac-core-0
- module.automation-tf-cicd-provisioning-sa.google_service_account.service_account[0]:
- account_id: fast-prod-cicd-0
- display_name: Terraform stage 1 CICD service account.
- project: fast-prod-iac-core-0
module.automation-tf-resman-sa.google_service_account.service_account[0]:
account_id: fast-prod-resman-0
display_name: Terraform stage 1 resman service account.
diff --git a/tests/fast/stages/s00_bootstrap/tftest.yaml b/tests/fast/stages/s0_bootstrap/tftest.yaml
similarity index 83%
rename from tests/fast/stages/s00_bootstrap/tftest.yaml
rename to tests/fast/stages/s0_bootstrap/tftest.yaml
index 4656859bc..b53749adc 100644
--- a/tests/fast/stages/s00_bootstrap/tftest.yaml
+++ b/tests/fast/stages/s0_bootstrap/tftest.yaml
@@ -1,6 +1,6 @@
# skip boilerplate check
-module: fast/stages/00-bootstrap
+module: fast/stages/0-bootstrap
tests:
simple:
diff --git a/tests/fast/stages/s02_networking_separate_envs/__init__.py b/tests/fast/stages/s1_resman/__init__.py
similarity index 100%
rename from tests/fast/stages/s02_networking_separate_envs/__init__.py
rename to tests/fast/stages/s1_resman/__init__.py
diff --git a/tests/fast/stages/s1_resman/common.tfvars b/tests/fast/stages/s1_resman/common.tfvars
new file mode 100644
index 000000000..34c61351e
--- /dev/null
+++ b/tests/fast/stages/s1_resman/common.tfvars
@@ -0,0 +1,28 @@
+automation = {
+ federated_identity_pool = null
+ federated_identity_providers = null
+ project_id = "fast-prod-automation"
+ project_number = 123456
+ outputs_bucket = "test"
+}
+billing_account = {
+ id = "000000-111111-222222"
+}
+custom_roles = {
+ # organization_iam_admin = "organizations/123456789012/roles/organizationIamAdmin",
+ service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin"
+}
+groups = {
+ gcp-billing-admins = "gcp-billing-admins",
+ gcp-devops = "gcp-devops",
+ gcp-network-admins = "gcp-network-admins",
+ gcp-organization-admins = "gcp-organization-admins",
+ gcp-security-admins = "gcp-security-admins",
+ gcp-support = "gcp-support"
+}
+organization = {
+ domain = "fast.example.com"
+ id = 123456789012
+ customer_id = "C00000000"
+}
+prefix = "fast2"
diff --git a/tests/fast/stages/s02_networking_separate_envs/test_plan.py b/tests/fast/stages/s1_resman/test_plan.py
similarity index 72%
rename from tests/fast/stages/s02_networking_separate_envs/test_plan.py
rename to tests/fast/stages/s1_resman/test_plan.py
index 1e697dc98..39bdfc11e 100644
--- a/tests/fast/stages/s02_networking_separate_envs/test_plan.py
+++ b/tests/fast/stages/s1_resman/test_plan.py
@@ -13,8 +13,9 @@
# limitations under the License.
-def test_counts(recursive_e2e_plan_runner):
+def test_counts(plan_summary):
"Test stage."
- num_modules, num_resources = recursive_e2e_plan_runner()
- # TODO: to re-enable per-module resource count check print _, then test
- assert num_modules > 0 and num_resources > 0
\ No newline at end of file
+ summary = plan_summary("fast/stages/1-resman",
+ tf_var_files=["common.tfvars"])
+ assert summary.counts["modules"] > 0
+ assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s02_networking_vpn/__init__.py b/tests/fast/stages/s2_networking_a_peering/__init__.py
similarity index 100%
rename from tests/fast/stages/s02_networking_vpn/__init__.py
rename to tests/fast/stages/s2_networking_a_peering/__init__.py
diff --git a/tests/fast/stages/s2_networking_a_peering/common.tfvars b/tests/fast/stages/s2_networking_a_peering/common.tfvars
new file mode 100644
index 000000000..6c2b0c030
--- /dev/null
+++ b/tests/fast/stages/s2_networking_a_peering/common.tfvars
@@ -0,0 +1,29 @@
+data_dir = "../../../fast/stages/2-networking-a-peering/data/"
+automation = {
+ outputs_bucket = "test"
+}
+billing_account = {
+ id = "000000-111111-222222"
+}
+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"
+ gke-dev = "string"
+ gke-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/s2_networking_a_peering/test_plan.py
similarity index 66%
rename from tests/fast/stages/s02_networking_peering/test_plan.py
rename to tests/fast/stages/s2_networking_a_peering/test_plan.py
index 917c90c15..09590d361 100644
--- a/tests/fast/stages/s02_networking_peering/test_plan.py
+++ b/tests/fast/stages/s2_networking_a_peering/test_plan.py
@@ -20,31 +20,34 @@ from deepdiff import DeepDiff
BASEDIR = Path(__file__).parent
FIXTURE_PEERING = BASEDIR / 'fixture'
-FIXTURE_VPN = BASEDIR.parent / 's02_networking_vpn/fixture'
+FIXTURE_VPN = BASEDIR.parent / 's2_networking_b_vpn/fixture'
STAGES = Path(__file__).parents[4] / 'fast/stages'
-STAGE_PEERING = STAGES / '02-networking-peering'
-STAGE_VPN = STAGES / '02-networking-vpn'
+STAGE_PEERING = STAGES / '2-networking-a-peering'
+STAGE_VPN = STAGES / '2-networking-b-vpn'
-def test_counts(recursive_e2e_plan_runner):
- 'Test stage.'
- num_modules, num_resources = recursive_e2e_plan_runner()
- # TODO: to re-enable per-module resource count check print _, then test
- assert num_modules > 0 and num_resources > 0
+def test_counts(plan_summary):
+ "Test stage."
+ summary = plan_summary("fast/stages/2-networking-a-peering",
+ tf_var_files=["common.tfvars"])
+ assert summary.counts["modules"] > 0
+ assert summary.counts["resources"] > 0
-def test_vpn_peering_parity(e2e_plan_runner):
+def test_vpn_peering_parity(plan_summary):
'''Ensure VPN- and peering-based networking stages are identical except
for VPN and VPC peering resources'''
- _, plan_peering = e2e_plan_runner(fixture_path=FIXTURE_PEERING)
- _, plan_vpn = e2e_plan_runner(fixture_path=FIXTURE_VPN)
+ summary_peering = plan_summary("fast/stages/2-networking-a-peering",
+ tf_var_files=["common.tfvars"])
+ summary_vpn = plan_summary("fast/stages/2-networking-b-vpn",
+ tf_var_files=["common.tfvars"])
- ddiff = DeepDiff(plan_vpn, plan_peering, ignore_order=True,
- group_by='address', view='tree')
+ ddiff = DeepDiff(summary_vpn.values, summary_peering.values,
+ ignore_order=True)
- removed_types = {x.t1['type'] for x in ddiff['dictionary_item_removed']}
- added_types = {x.t2['type'] for x in ddiff['dictionary_item_added']}
+ removed_types = {x.split('.')[-2] for x in ddiff['dictionary_item_removed']}
+ added_types = {x.split('.')[-2] for x in ddiff['dictionary_item_added']}
assert added_types == {'google_compute_network_peering'}
assert removed_types == {
diff --git a/tests/fast/stages/s02_security/__init__.py b/tests/fast/stages/s2_networking_b_vpn/__init__.py
similarity index 100%
rename from tests/fast/stages/s02_security/__init__.py
rename to tests/fast/stages/s2_networking_b_vpn/__init__.py
diff --git a/tests/fast/stages/s2_networking_b_vpn/common.tfvars b/tests/fast/stages/s2_networking_b_vpn/common.tfvars
new file mode 100644
index 000000000..66a7a6090
--- /dev/null
+++ b/tests/fast/stages/s2_networking_b_vpn/common.tfvars
@@ -0,0 +1,34 @@
+data_dir = "../../../../../fast/stages/2-networking-b-vpn/data/"
+automation = {
+ outputs_bucket = "test"
+}
+billing_account = {
+ id = "000000-111111-222222"
+}
+custom_roles = {
+ service_project_network_admin = "organizations/123456789012/roles/foo"
+}
+folder_ids = {
+ networking = null
+ networking-dev = null
+ networking-prod = null
+}
+region_trigram = {
+ europe-west1 = "ew1"
+ europe-west3 = "ew3"
+ europe-west8 = "ew8"
+}
+service_accounts = {
+ data-platform-dev = "string"
+ data-platform-prod = "string"
+ gke-dev = "string"
+ gke-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_vpn/fixture/main.tf b/tests/fast/stages/s2_networking_b_vpn/fixture/main.tf
similarity index 100%
rename from tests/fast/stages/s02_networking_vpn/fixture/main.tf
rename to tests/fast/stages/s2_networking_b_vpn/fixture/main.tf
diff --git a/tests/fast/stages/s02_networking_nva/test_plan.py b/tests/fast/stages/s2_networking_b_vpn/test_plan.py
similarity index 72%
rename from tests/fast/stages/s02_networking_nva/test_plan.py
rename to tests/fast/stages/s2_networking_b_vpn/test_plan.py
index 5f1058065..8ac1bade1 100644
--- a/tests/fast/stages/s02_networking_nva/test_plan.py
+++ b/tests/fast/stages/s2_networking_b_vpn/test_plan.py
@@ -13,8 +13,9 @@
# limitations under the License.
-def test_counts(recursive_e2e_plan_runner):
+def test_counts(plan_summary):
"Test stage."
- num_modules, num_resources = recursive_e2e_plan_runner()
- # TODO: to re-enable per-module resource count check print _, then test
- assert num_modules > 0 and num_resources > 0
+ summary = plan_summary("fast/stages/2-networking-b-vpn",
+ tf_var_files=["common.tfvars"])
+ assert summary.counts["modules"] > 0
+ assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s03_data_platform/__init__.py b/tests/fast/stages/s2_networking_c_nva/__init__.py
similarity index 100%
rename from tests/fast/stages/s03_data_platform/__init__.py
rename to tests/fast/stages/s2_networking_c_nva/__init__.py
diff --git a/tests/fast/stages/s2_networking_c_nva/common.tfvars b/tests/fast/stages/s2_networking_c_nva/common.tfvars
new file mode 100644
index 000000000..ad12b8d33
--- /dev/null
+++ b/tests/fast/stages/s2_networking_c_nva/common.tfvars
@@ -0,0 +1,29 @@
+data_dir = "../../../fast/stages/2-networking-c-nva/data/"
+automation = {
+ outputs_bucket = "test"
+}
+billing_account = {
+ id = "000000-111111-222222"
+}
+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"
+ gke-dev = "string"
+ gke-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_vpn/test_plan.py b/tests/fast/stages/s2_networking_c_nva/test_plan.py
similarity index 72%
rename from tests/fast/stages/s02_networking_vpn/test_plan.py
rename to tests/fast/stages/s2_networking_c_nva/test_plan.py
index 5f1058065..70c37bc38 100644
--- a/tests/fast/stages/s02_networking_vpn/test_plan.py
+++ b/tests/fast/stages/s2_networking_c_nva/test_plan.py
@@ -13,8 +13,9 @@
# limitations under the License.
-def test_counts(recursive_e2e_plan_runner):
+def test_counts(plan_summary):
"Test stage."
- num_modules, num_resources = recursive_e2e_plan_runner()
- # TODO: to re-enable per-module resource count check print _, then test
- assert num_modules > 0 and num_resources > 0
+ summary = plan_summary("fast/stages/2-networking-c-nva",
+ tf_var_files=["common.tfvars"])
+ assert summary.counts["modules"] > 0
+ assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s03_gke_multitenant/__init__.py b/tests/fast/stages/s2_networking_d_separate_envs/__init__.py
similarity index 100%
rename from tests/fast/stages/s03_gke_multitenant/__init__.py
rename to tests/fast/stages/s2_networking_d_separate_envs/__init__.py
diff --git a/tests/fast/stages/s2_networking_d_separate_envs/common.tfvars b/tests/fast/stages/s2_networking_d_separate_envs/common.tfvars
new file mode 100644
index 000000000..3ff0020ac
--- /dev/null
+++ b/tests/fast/stages/s2_networking_d_separate_envs/common.tfvars
@@ -0,0 +1,27 @@
+data_dir = "../../../../../fast/stages/2-networking-d-separate-envs/data/"
+automation = {
+ outputs_bucket = "test"
+}
+billing_account = {
+ id = "000000-111111-222222"
+}
+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/s2_networking_d_separate_envs/test_plan.py b/tests/fast/stages/s2_networking_d_separate_envs/test_plan.py
new file mode 100644
index 000000000..51a9257af
--- /dev/null
+++ b/tests/fast/stages/s2_networking_d_separate_envs/test_plan.py
@@ -0,0 +1,21 @@
+# 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(plan_summary):
+ "Test stage."
+ summary = plan_summary("fast/stages/2-networking-d-separate-envs",
+ tf_var_files=["common.tfvars"])
+ assert summary.counts["modules"] > 0
+ assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s03_project_factory/__init__.py b/tests/fast/stages/s2_security/__init__.py
similarity index 100%
rename from tests/fast/stages/s03_project_factory/__init__.py
rename to tests/fast/stages/s2_security/__init__.py
diff --git a/tests/fast/stages/s2_security/common.tfvars b/tests/fast/stages/s2_security/common.tfvars
new file mode 100644
index 000000000..6fbb60b64
--- /dev/null
+++ b/tests/fast/stages/s2_security/common.tfvars
@@ -0,0 +1,87 @@
+automation = {
+ outputs_bucket = "test"
+}
+billing_account = {
+ id = "000000-111111-222222"
+}
+folder_ids = {
+ security = null
+}
+organization = {
+ domain = "fast.example.com"
+ id = 123456789012
+ customer_id = "C00000000"
+}
+prefix = "fast"
+kms_keys = {
+ compute = {
+ iam = {
+ "roles/cloudkms.admin" = ["user:user1@example.com"]
+ }
+ labels = { service = "compute" }
+ locations = null
+ rotation_period = null
+ }
+}
+service_accounts = {
+ security = "foobar@iam.gserviceaccount.com"
+ data-platform-dev = "foobar@iam.gserviceaccount.com"
+ data-platform-prod = "foobar@iam.gserviceaccount.com"
+ project-factory-dev = "foobar@iam.gserviceaccount.com"
+ project-factory-prod = "foobar@iam.gserviceaccount.com"
+}
+vpc_sc_access_levels = {
+ onprem = {
+ conditions = [{
+ ip_subnetworks = ["101.101.101.0/24"]
+ }]
+ }
+}
+vpc_sc_egress_policies = {
+ iac-gcs = {
+ from = {
+ identities = [
+ "serviceAccount:xxx-prod-resman-security-0@xxx-prod-iac-core-0.iam.gserviceaccount.com"
+ ]
+ }
+ to = {
+ operations = [{
+ method_selectors = ["*"]
+ service_name = "storage.googleapis.com"
+ }]
+ resources = ["projects/123456782"]
+ }
+ }
+}
+vpc_sc_ingress_policies = {
+ iac = {
+ from = {
+ identities = [
+ "serviceAccount:xxx-prod-resman-security-0@xxx-prod-iac-core-0.iam.gserviceaccount.com"
+ ]
+ access_levels = ["*"]
+ }
+ to = {
+ operations = [{ method_selectors = [], service_name = "*" }]
+ resources = ["*"]
+ }
+ }
+}
+vpc_sc_perimeters = {
+ dev = {
+ egress_policies = ["iac-gcs"]
+ ingress_policies = ["iac"]
+ resources = ["projects/1111111111"]
+ }
+ dev = {
+ egress_policies = ["iac-gcs"]
+ ingress_policies = ["iac"]
+ resources = ["projects/0000000000"]
+ }
+ dev = {
+ access_levels = ["onprem"]
+ egress_policies = ["iac-gcs"]
+ ingress_policies = ["iac"]
+ resources = ["projects/2222222222"]
+ }
+}
diff --git a/tests/fast/stages/s01_resman/test_plan.py b/tests/fast/stages/s2_security/test_plan.py
similarity index 72%
rename from tests/fast/stages/s01_resman/test_plan.py
rename to tests/fast/stages/s2_security/test_plan.py
index 5f1058065..edf5622e7 100644
--- a/tests/fast/stages/s01_resman/test_plan.py
+++ b/tests/fast/stages/s2_security/test_plan.py
@@ -13,8 +13,9 @@
# limitations under the License.
-def test_counts(recursive_e2e_plan_runner):
+def test_counts(plan_summary):
"Test stage."
- num_modules, num_resources = recursive_e2e_plan_runner()
- # TODO: to re-enable per-module resource count check print _, then test
- assert num_modules > 0 and num_resources > 0
+ summary = plan_summary("fast/stages/2-security",
+ tf_var_files=["common.tfvars"])
+ assert summary.counts["modules"] > 0
+ assert summary.counts["resources"] > 0
diff --git a/tests/modules/api_gateway/__init__.py b/tests/fast/stages/s3_data_platform/__init__.py
similarity index 100%
rename from tests/modules/api_gateway/__init__.py
rename to tests/fast/stages/s3_data_platform/__init__.py
diff --git a/tests/fast/stages/s3_data_platform/common.tfvars b/tests/fast/stages/s3_data_platform/common.tfvars
new file mode 100644
index 000000000..2ec41d37a
--- /dev/null
+++ b/tests/fast/stages/s3_data_platform/common.tfvars
@@ -0,0 +1,25 @@
+automation = {
+ outputs_bucket = "test"
+}
+billing_account = {
+ id = "012345-67890A-BCDEF0",
+}
+folder_ids = {
+ data-platform-dev = "folders/12345678"
+}
+host_project_ids = {
+ dev-spoke-0 = "fast-dev-net-spoke-0"
+}
+organization = {
+ domain = "fast.example.com"
+ id = 123456789012
+ customer_id = "C00000000"
+}
+prefix = "fast"
+subnet_self_links = {
+ dev-spoke-0 = {
+ "europe-west1/dev-dataplatform-ew1" : "https://www.googleapis.com/compute/v1/projects/fast-dev-net-spoke-0/regions/europe-west1/subnetworks/dev-dataplatform-ew1",
+ "europe-west1/dev-default-ew1" : "https://www.googleapis.com/compute/v1/projects/fast-dev-net-spoke-0/regions/europe-west1/subnetworks/dev-default-ew1"
+ }
+}
+vpc_self_links = { dev-spoke-0 = "https://www.googleapis.com/compute/v1/projects/fast-dev-net-spoke-0/global/networks/dev-spoke-0" }
diff --git a/tests/fast/stages/s3_data_platform/test_plan.py b/tests/fast/stages/s3_data_platform/test_plan.py
new file mode 100644
index 000000000..ad7fa3d28
--- /dev/null
+++ b/tests/fast/stages/s3_data_platform/test_plan.py
@@ -0,0 +1,21 @@
+# 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(plan_summary):
+ "Test stage."
+ summary = plan_summary("fast/stages/3-data-platform/dev/",
+ tf_var_files=["common.tfvars"])
+ assert summary.counts["modules"] > 0
+ assert summary.counts["resources"] > 0
diff --git a/tests/modules/compute_vm/__init__.py b/tests/fast/stages/s3_gke_multitenant/__init__.py
similarity index 100%
rename from tests/modules/compute_vm/__init__.py
rename to tests/fast/stages/s3_gke_multitenant/__init__.py
diff --git a/tests/fast/stages/s3_gke_multitenant/common.tfvars b/tests/fast/stages/s3_gke_multitenant/common.tfvars
new file mode 100644
index 000000000..1cafdd9ab
--- /dev/null
+++ b/tests/fast/stages/s3_gke_multitenant/common.tfvars
@@ -0,0 +1,40 @@
+automation = {
+ outputs_bucket = "test"
+}
+billing_account = {
+ id = "012345-67890A-BCDEF0",
+}
+clusters = {
+ mycluster = {
+ cluster_autoscaling = null
+ description = "my cluster"
+ dns_domain = null
+ location = "europe-west1"
+ labels = {}
+ private_cluster_config = {
+ enable_private_endpoint = true
+ master_global_access = true
+ }
+ vpc_config = {
+ subnetwork = "projects/prj-host/regions/europe-west1/subnetworks/gke-0"
+ master_ipv4_cidr_block = "172.16.20.0/28"
+ }
+ }
+}
+nodepools = {
+ mycluster = {
+ mynodepool = {
+ node_count = { initial = 1 }
+ }
+ }
+}
+folder_ids = {
+ gke-dev = "folders/12345678"
+}
+host_project_ids = {
+ dev-spoke-0 = "fast-dev-net-spoke-0"
+}
+prefix = "fast"
+vpc_self_links = {
+ dev-spoke-0 = "projects/fast-dev-net-spoke-0/global/networks/dev-spoke-0"
+}
diff --git a/tests/fast/stages/s3_gke_multitenant/test_plan.py b/tests/fast/stages/s3_gke_multitenant/test_plan.py
new file mode 100644
index 000000000..c517cb933
--- /dev/null
+++ b/tests/fast/stages/s3_gke_multitenant/test_plan.py
@@ -0,0 +1,21 @@
+# 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(plan_summary):
+ "Test stage."
+ summary = plan_summary("fast/stages/3-gke-multitenant/dev/",
+ tf_var_files=["common.tfvars"])
+ assert summary.counts["modules"] > 0
+ assert summary.counts["resources"] > 0
diff --git a/tests/modules/dns/__init__.py b/tests/fast/stages/s3_project_factory/__init__.py
similarity index 100%
rename from tests/modules/dns/__init__.py
rename to tests/fast/stages/s3_project_factory/__init__.py
diff --git a/tests/fast/stages/s3_project_factory/common.tfvars b/tests/fast/stages/s3_project_factory/common.tfvars
new file mode 100644
index 000000000..d3f8c6f9a
--- /dev/null
+++ b/tests/fast/stages/s3_project_factory/common.tfvars
@@ -0,0 +1,10 @@
+data_dir = "../../../../tests/fast/stages/s3_project_factory/data/projects/"
+defaults_file = "../../../../tests/fast/stages/s3_project_factory/data/defaults.yaml"
+prefix = "test"
+environment_dns_zone = "dev"
+billing_account = {
+ id = "000000-111111-222222"
+}
+vpc_self_links = {
+ dev-spoke-0 = "link"
+}
diff --git a/tests/fast/stages/s03_project_factory/fixture/data/defaults.yaml b/tests/fast/stages/s3_project_factory/data/defaults.yaml
similarity index 100%
rename from tests/fast/stages/s03_project_factory/fixture/data/defaults.yaml
rename to tests/fast/stages/s3_project_factory/data/defaults.yaml
diff --git a/tests/fast/stages/s03_project_factory/fixture/data/projects/project.yaml b/tests/fast/stages/s3_project_factory/data/projects/project.yaml
similarity index 100%
rename from tests/fast/stages/s03_project_factory/fixture/data/projects/project.yaml
rename to tests/fast/stages/s3_project_factory/data/projects/project.yaml
diff --git a/tests/fast/stages/s3_project_factory/test_plan.py b/tests/fast/stages/s3_project_factory/test_plan.py
new file mode 100644
index 000000000..fa293da84
--- /dev/null
+++ b/tests/fast/stages/s3_project_factory/test_plan.py
@@ -0,0 +1,21 @@
+# 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(plan_summary):
+ "Test stage."
+ summary = plan_summary("fast/stages/3-project-factory/dev",
+ tf_var_files=["common.tfvars"])
+ assert summary.counts["modules"] > 0
+ assert summary.counts["resources"] > 0
diff --git a/tests/modules/gcs/__init__.py b/tests/fast/stages_multitenant/__init__.py
similarity index 100%
rename from tests/modules/gcs/__init__.py
rename to tests/fast/stages_multitenant/__init__.py
diff --git a/tests/modules/gke_cluster/__init__.py b/tests/fast/stages_multitenant/s0_bootstrap_tenant/__init__.py
similarity index 100%
rename from tests/modules/gke_cluster/__init__.py
rename to tests/fast/stages_multitenant/s0_bootstrap_tenant/__init__.py
diff --git a/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.tfvars b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.tfvars
new file mode 100644
index 000000000..52ca76a3d
--- /dev/null
+++ b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.tfvars
@@ -0,0 +1,61 @@
+automation = {
+ federated_identity_pool = null
+ federated_identity_providers = null
+ project_id = "fast-prod-automation"
+ project_number = 123456
+ outputs_bucket = "test"
+}
+billing_account = {
+ id = "000000-111111-222222"
+}
+custom_roles = {
+ # organization_iam_admin = "organizations/123456789012/roles/organizationIamAdmin",
+ service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin"
+ tenant_network_admin = "organizations/123456789012/roles/TenantNetworkAdmin"
+}
+groups = {
+ gcp-billing-admins = "gcp-billing-admins",
+ gcp-devops = "gcp-devops",
+ gcp-network-admins = "gcp-network-admins",
+ gcp-organization-admins = "gcp-organization-admins",
+ gcp-security-admins = "gcp-security-admins",
+ gcp-support = "gcp-support"
+}
+organization = {
+ domain = "fast.example.com"
+ id = 123456789012
+ customer_id = "C00000000"
+}
+prefix = "fast2"
+tag_keys = {
+ context = "tagKeys/1234567890"
+ environment = "tagKeys/4567890123"
+ tenant = "tagKeys/7890123456"
+}
+tag_names = {
+ context = "context"
+ environment = "environment"
+ tenant = "tenant"
+}
+tag_values = {
+ "context/data" : "tagValues/1234567890",
+ "context/gke" : "tagValues/1234567890",
+ "context/networking" : "tagValues/1234567890",
+ "context/sandbox" : "tagValues/1234567890",
+ "context/security" : "tagValues/1234567890",
+ "context/teams" : "tagValues/1234567890",
+ "environment/development" : "tagValues/1234567890",
+ "environment/production" : "tagValues/1234567890"
+}
+tenant_config = {
+ groups = {
+ gcp-admins = "gcp-tn01-admins"
+ }
+ descriptive_name = "Tenant 01"
+ locations = {
+ gcs = "europe-west8"
+ logging = "europe-west8"
+ }
+ short_name = "tn01"
+}
+test_principal = "foo-prod-resman-0@foo-prod-iac-core-0.iam.gserviceaccount.com"
diff --git a/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.yaml b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.yaml
new file mode 100644
index 000000000..e5ccc4fd5
--- /dev/null
+++ b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.yaml
@@ -0,0 +1,33 @@
+# 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.
+
+counts:
+ google_bigquery_default_service_account: 2
+ google_folder: 2
+ google_folder_iam_binding: 5
+ google_organization_iam_member: 39
+ google_project: 2
+ google_project_iam_binding: 8
+ google_project_service: 26
+ google_project_service_identity: 3
+ google_service_account: 11
+ google_storage_bucket: 2
+ google_storage_bucket_iam_binding: 1
+ google_storage_bucket_iam_member: 1
+ google_storage_bucket_object: 2
+ google_storage_project_service_account: 2
+ google_tags_tag_binding: 1
+ google_tags_tag_value: 1
+ modules: 19
+ resources: 129
diff --git a/tests/fast/stages_multitenant/s0_bootstrap_tenant/tftest.yaml b/tests/fast/stages_multitenant/s0_bootstrap_tenant/tftest.yaml
new file mode 100644
index 000000000..c2fa9fa8e
--- /dev/null
+++ b/tests/fast/stages_multitenant/s0_bootstrap_tenant/tftest.yaml
@@ -0,0 +1,10 @@
+# skip boilerplate check
+
+module: fast/stages-multitenant/0-bootstrap-tenant
+
+tests:
+ simple:
+ tfvars:
+ - simple.tfvars
+ inventory:
+ - simple.yaml
diff --git a/tests/modules/gke_nodepool/__init__.py b/tests/fast/stages_multitenant/s1_resman_tenant/__init__.py
similarity index 100%
rename from tests/modules/gke_nodepool/__init__.py
rename to tests/fast/stages_multitenant/s1_resman_tenant/__init__.py
diff --git a/tests/fast/stages_multitenant/s1_resman_tenant/simple.tfvars b/tests/fast/stages_multitenant/s1_resman_tenant/simple.tfvars
new file mode 100644
index 000000000..33cf46198
--- /dev/null
+++ b/tests/fast/stages_multitenant/s1_resman_tenant/simple.tfvars
@@ -0,0 +1,70 @@
+automation = {
+ federated_identity_pools = null
+ federated_identity_providers = null
+ project_id = "tn0-prod-automation-0"
+ project_number = 123456
+ outputs_bucket = "tn0-prod-automation-0"
+ service_accounts = {
+ networking = "foo-tn0-net-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ resman = "foo-tn0-resman-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ security = "foo-tn0-sec-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ dp-dev = "foo-tn0-dp-dev-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ dp-prod = "foo-tn0-dp-prod-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ gke-dev = "foo-tn0-gke-dev-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ gke-prod = "foo-tn0-gke-prod-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ pf-dev = "foo-tn0-pf-dev-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ pf-prod = "foo-tn0-pf-prod-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ sandbox = "foo-tn0-sandbox-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ teams = "foo-tn0-teams-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ }
+}
+billing_account = {
+ id = "000000-111111-222222"
+}
+custom_roles = {
+ # organization_iam_admin = "organizations/123456789012/roles/organizationIamAdmin",
+ service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin"
+}
+fast_features = {
+ data_platform = true
+ gke = true
+ project_factory = true
+ sandbox = true
+ teams = true
+}
+groups = {
+ gcp-devops = "gcp-devops",
+ gcp-network-admins = "gcp-network-admins",
+ gcp-security-admins = "gcp-security-admins",
+}
+organization = {
+ domain = "fast.example.com"
+ id = 123456789012
+ customer_id = "C00000000"
+}
+prefix = "foo-tn0"
+root_node = "folders/1234567890"
+short_name = "tn0"
+tags = {
+ keys = {
+ context = "tagKeys/1234567890"
+ environment = "tagKeys/4567890123"
+ tenant = "tagKeys/7890123456"
+ }
+ names = {
+ context = "context"
+ environment = "environment"
+ tenant = "tenant"
+ }
+ values = {
+ "context/data" : "tagValues/1234567890",
+ "context/gke" : "tagValues/1234567890",
+ "context/networking" : "tagValues/1234567890",
+ "context/sandbox" : "tagValues/1234567890",
+ "context/security" : "tagValues/1234567890",
+ "context/teams" : "tagValues/1234567890",
+ "environment/development" : "tagValues/1234567890",
+ "environment/production" : "tagValues/1234567890"
+ }
+}
+test_skip_data_sources = true
diff --git a/tests/fast/stages_multitenant/s1_resman_tenant/simple.yaml b/tests/fast/stages_multitenant/s1_resman_tenant/simple.yaml
new file mode 100644
index 000000000..44c07c62f
--- /dev/null
+++ b/tests/fast/stages_multitenant/s1_resman_tenant/simple.yaml
@@ -0,0 +1,28 @@
+# 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.
+
+counts:
+ google_folder: 13
+ google_folder_iam_binding: 42
+ google_folder_iam_member: 3
+ google_org_policy_policy: 2
+ google_service_account: 9
+ google_service_account_iam_binding: 8
+ google_storage_bucket: 10
+ google_storage_bucket_iam_binding: 10
+ google_storage_bucket_iam_member: 9
+ google_storage_bucket_object: 11
+ google_tags_tag_binding: 12
+ modules: 32
+ resources: 129
diff --git a/tests/fast/stages_multitenant/s1_resman_tenant/tftest.yaml b/tests/fast/stages_multitenant/s1_resman_tenant/tftest.yaml
new file mode 100644
index 000000000..2e107e08d
--- /dev/null
+++ b/tests/fast/stages_multitenant/s1_resman_tenant/tftest.yaml
@@ -0,0 +1,10 @@
+# skip boilerplate check
+
+module: fast/stages-multitenant/1-resman-tenant
+
+tests:
+ simple:
+ tfvars:
+ - simple.tfvars
+ inventory:
+ - simple.yaml
diff --git a/tests/fixtures.py b/tests/fixtures.py
index c483063f4..11a397de7 100644
--- a/tests/fixtures.py
+++ b/tests/fixtures.py
@@ -31,7 +31,7 @@ PlanSummary = collections.namedtuple('PlanSummary', 'values counts outputs')
@contextlib.contextmanager
def _prepare_root_module(path):
"""Context manager to prepare a terraform module to be tested.
-
+
If the TFTEST_COPY environment variable is set, `path` is copied to
a temporary directory and a few terraform files (e.g.
terraform.tfvars) are delete to ensure a clean test environment.
@@ -49,6 +49,7 @@ def _prepare_root_module(path):
# deployment with links to configs)
ignore_patterns = shutil.ignore_patterns('*.auto.tfvars',
'*.auto.tfvars.json',
+ '[0-9]-*-providers.tf',
'terraform.tfstate*',
'terraform.tfvars', '.terraform')
@@ -103,6 +104,7 @@ def plan_summary(module_path, basedir, tf_var_files=None, **tf_vars):
# compute resource type counts and address->values map
values = {}
counts = collections.defaultdict(int)
+ counts['modules'] = counts['resources'] = 0
q = collections.deque([plan.root_module])
while q:
e = q.popleft()
@@ -113,8 +115,10 @@ def plan_summary(module_path, basedir, tf_var_files=None, **tf_vars):
values[e['address']] = e['values']
for x in e.get('resources', []):
+ counts['resources'] += 1
q.append(x)
for x in e.get('child_modules', []):
+ counts['modules'] += 1
q.append(x)
# extract planned outputs
@@ -177,19 +181,19 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None,
expected_values = inventory['values']
for address, expected_value in expected_values.items():
assert address in summary.values, \
- f'{address} is not a valid address in the plan'
+ f'{address} is not a valid address in the plan'
for k, v in expected_value.items():
assert k in summary.values[address], \
- f'{k} not found at {address}'
+ f'{k} not found at {address}'
plan_value = summary.values[address][k]
assert plan_value == v, \
- f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`'
+ f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`'
if 'counts' in inventory:
expected_counts = inventory['counts']
for type_, expected_count in expected_counts.items():
assert type_ in summary.counts, \
- f'module does not create any resources of type `{type_}`'
+ f'module does not create any resources of type `{type_}`'
plan_count = summary.counts[type_]
assert plan_count == expected_count, \
f'count of {type_} resources failed. Got {plan_count}, expected {expected_count}'
@@ -198,7 +202,7 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None,
expected_outputs = inventory['outputs']
for output_name, expected_output in expected_outputs.items():
assert output_name in summary.outputs, \
- f'module does not output `{output_name}`'
+ f'module does not output `{output_name}`'
output = summary.outputs[output_name]
# assert 'value' in output, \
# f'output `{output_name}` does not have a value (is it sensitive or dynamic?)'
@@ -224,7 +228,7 @@ def plan_validator_fixture(request):
basedir = Path(request.fspath).parent
return plan_validator(module_path=module_path,
inventory_paths=inventory_paths, basedir=basedir,
- tf_var_files=tf_var_paths, **tf_vars)
+ tf_var_files=tf_var_files, **tf_vars)
return inner
diff --git a/tests/legacy_fixtures.py b/tests/legacy_fixtures.py
index 5891d704b..8b23ea5ee 100644
--- a/tests/legacy_fixtures.py
+++ b/tests/legacy_fixtures.py
@@ -96,34 +96,6 @@ def e2e_plan_runner(_plan_runner):
return run_plan
-@pytest.fixture(scope='session')
-def recursive_e2e_plan_runner(_plan_runner):
- """
- Plan runner for end-to-end root module, returns total number of
- (nested) modules and resources
- """
-
- def walk_plan(node, modules, resources):
- new_modules = node.get('child_modules', [])
- resources += node.get('resources', [])
- modules += new_modules
- for module in new_modules:
- walk_plan(module, modules, resources)
-
- def run_plan(fixture_path=None, tf_var_file=None, targets=None, refresh=True,
- include_bare_resources=False, compute_sums=True, tmpdir=True,
- **tf_vars):
- 'Run Terraform plan on a root module using defaults, returns data.'
- plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets,
- refresh=refresh, tmpdir=tmpdir, **tf_vars)
- modules = []
- resources = []
- walk_plan(plan.root_module, modules, resources)
- return len(modules), len(resources)
-
- return run_plan
-
-
@pytest.fixture(scope='session')
def apply_runner():
'Return a function to run Terraform apply on a fixture.'
diff --git a/tests/modules/api_gateway/examples/basic.yaml b/tests/modules/api_gateway/examples/basic.yaml
new file mode 100644
index 000000000..a17fc3ca4
--- /dev/null
+++ b/tests/modules/api_gateway/examples/basic.yaml
@@ -0,0 +1,42 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.gateway.google_api_gateway_api.api:
+ api_id: api
+ display_name: api
+ project: my-project
+ module.gateway.google_api_gateway_api_config.api_config:
+ api: api
+ gateway_config: []
+ grpc_services: []
+ labels: null
+ managed_service_configs: []
+ project: my-project
+ module.gateway.google_api_gateway_gateway.gateway:
+ display_name: gw-api
+ gateway_id: gw-api
+ labels: null
+ project: my-project
+ region: europe-west1
+ module.gateway.google_project_service.service:
+ disable_dependent_services: true
+ disable_on_destroy: true
+ project: my-project
+
+counts:
+ google_api_gateway_api: 1
+ google_api_gateway_api_config: 1
+ google_api_gateway_gateway: 1
+ google_project_service: 1
diff --git a/tests/modules/api_gateway/examples/create-sa.yaml b/tests/modules/api_gateway/examples/create-sa.yaml
new file mode 100644
index 000000000..2c8d7c763
--- /dev/null
+++ b/tests/modules/api_gateway/examples/create-sa.yaml
@@ -0,0 +1,90 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.gateway.google_api_gateway_api.api:
+ api_id: api
+ display_name: api
+ labels: null
+ project: my-project
+ module.gateway.google_api_gateway_api_config.api_config:
+ api: api
+ grpc_services: []
+ labels: null
+ managed_service_configs: []
+ project: my-project
+ module.gateway.google_api_gateway_api_config_iam_binding.api_config_iam_bindings["roles/apigateway.admin"]:
+ api: api
+ condition: []
+ members:
+ - user:mirene@google.com
+ project: my-project
+ role: roles/apigateway.admin
+ module.gateway.google_api_gateway_api_config_iam_binding.api_config_iam_bindings["roles/apigateway.viewer"]:
+ api: api
+ condition: []
+ members:
+ - user:mirene@google.com
+ project: my-project
+ role: roles/apigateway.viewer
+ module.gateway.google_api_gateway_api_iam_binding.api_iam_bindings["roles/apigateway.admin"]:
+ api: api
+ condition: []
+ members:
+ - user:mirene@google.com
+ project: my-project
+ role: roles/apigateway.admin
+ module.gateway.google_api_gateway_api_iam_binding.api_iam_bindings["roles/apigateway.viewer"]:
+ api: api
+ condition: []
+ members:
+ - user:mirene@google.com
+ project: my-project
+ role: roles/apigateway.viewer
+ module.gateway.google_api_gateway_gateway.gateway:
+ display_name: gw-api
+ gateway_id: gw-api
+ labels: null
+ project: my-project
+ region: europe-west1
+ module.gateway.google_api_gateway_gateway_iam_binding.gateway_iam_bindings["roles/apigateway.admin"]:
+ condition: []
+ gateway: gw-api
+ members:
+ - user:mirene@google.com
+ project: my-project
+ region: europe-west1
+ role: roles/apigateway.admin
+ module.gateway.google_api_gateway_gateway_iam_binding.gateway_iam_bindings["roles/apigateway.viewer"]:
+ condition: []
+ gateway: gw-api
+ members:
+ - user:mirene@google.com
+ project: my-project
+ region: europe-west1
+ role: roles/apigateway.viewer
+ module.gateway.google_project_service.service: {}
+ module.gateway.google_service_account.service_account[0]:
+ account_id: sa-api-cfg-api
+ project: my-project
+
+counts:
+ google_api_gateway_api: 1
+ google_api_gateway_api_config: 1
+ google_api_gateway_api_config_iam_binding: 2
+ google_api_gateway_api_iam_binding: 2
+ google_api_gateway_gateway: 1
+ google_api_gateway_gateway_iam_binding: 2
+ google_project_service: 1
+ google_service_account: 1
diff --git a/tests/modules/api_gateway/examples/existing-sa.yaml b/tests/modules/api_gateway/examples/existing-sa.yaml
new file mode 100644
index 000000000..f0befa79a
--- /dev/null
+++ b/tests/modules/api_gateway/examples/existing-sa.yaml
@@ -0,0 +1,71 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.gateway.google_api_gateway_api.api:
+ api_id: api
+ display_name: api
+ labels: null
+ project: my-project
+ module.gateway.google_api_gateway_api_config.api_config:
+ api: api
+ gateway_config:
+ - backend_config:
+ - google_service_account: sa@my-project.iam.gserviceaccount.com
+ grpc_services: []
+ labels: null
+ managed_service_configs: []
+ project: my-project
+ module.gateway.google_api_gateway_api_config_iam_binding.api_config_iam_bindings["roles/apigateway.admin"]:
+ api: api
+ api_config: api-cfg-api-8656c6040d6d9ba18a8b9b5f3955c223
+ condition: []
+ members:
+ - user:user@example.com
+ project: my-project
+ role: roles/apigateway.admin
+ module.gateway.google_api_gateway_api_iam_binding.api_iam_bindings["roles/apigateway.admin"]:
+ api: api
+ condition: []
+ members:
+ - user:user@example.com
+ project: my-project
+ role: roles/apigateway.admin
+ module.gateway.google_api_gateway_gateway.gateway:
+ display_name: gw-api
+ gateway_id: gw-api
+ labels: null
+ project: my-project
+ region: europe-west1
+ module.gateway.google_api_gateway_gateway_iam_binding.gateway_iam_bindings["roles/apigateway.admin"]:
+ condition: []
+ gateway: gw-api
+ members:
+ - user:user@example.com
+ project: my-project
+ region: europe-west1
+ role: roles/apigateway.admin
+ module.gateway.google_project_service.service:
+ disable_dependent_services: true
+ disable_on_destroy: true
+ project: my-project
+
+counts:
+ google_api_gateway_api: 1
+ google_api_gateway_api_config: 1
+ google_api_gateway_api_config_iam_binding: 1
+ google_api_gateway_api_iam_binding: 1
+ google_api_gateway_gateway: 1
+ google_api_gateway_gateway_iam_binding: 1
+ google_project_service: 1
diff --git a/tests/modules/api_gateway/fixture/main.tf b/tests/modules/api_gateway/fixture/main.tf
deleted file mode 100644
index d4cd134f2..000000000
--- a/tests/modules/api_gateway/fixture/main.tf
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * 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 "gateway" {
- source = "../../../../modules/api-gateway"
- api_id = var.api_id
- project_id = var.project_id
- labels = var.labels
- iam = var.iam
- region = var.region
- spec = var.spec
- service_account_create = true
-}
diff --git a/tests/modules/api_gateway/fixture/variables.tf b/tests/modules/api_gateway/fixture/variables.tf
deleted file mode 100644
index 977af921d..000000000
--- a/tests/modules/api_gateway/fixture/variables.tf
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * 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 "api_id" {
- type = string
- default = "my-api"
-}
-
-variable "iam" {
- type = map(list(string))
- default = null
-}
-
-variable "labels" {
- type = map(string)
- default = null
-}
-
-variable "project_id" {
- type = string
- default = "my-project"
-}
-
-variable "region" {
- type = string
- default = "europe-west1"
-}
-
-variable "service_account_create" {
- type = bool
- default = true
-}
-
-variable "service_account_email" {
- type = string
- default = null
-}
-
-variable "spec" {
- type = string
- default = "Spec contents"
-}
diff --git a/tests/modules/apigee/fixture/test.all.tfvars b/tests/modules/apigee/fixture/test.all.tfvars
index d0c29921c..9eb337b74 100644
--- a/tests/modules/apigee/fixture/test.all.tfvars
+++ b/tests/modules/apigee/fixture/test.all.tfvars
@@ -29,14 +29,16 @@ environments = {
}
instances = {
instance-test-ew1 = {
- region = "europe-west1"
- environments = ["apis-test"]
- psa_ip_cidr_range = "10.0.4.0/22"
+ region = "europe-west1"
+ environments = ["apis-test"]
+ runtime_ip_cidr_range = "10.0.4.0/22"
+ troubleshooting_ip_cidr_range = "10.1.0.0/28"
}
instance-prod-ew3 = {
- region = "europe-west3"
- environments = ["apis-prod"]
- psa_ip_cidr_range = "10.0.5.0/22"
+ region = "europe-west3"
+ environments = ["apis-prod"]
+ runtime_ip_cidr_range = "10.0.6.0/22"
+ troubleshooting_ip_cidr_range = "10.1.0.16/28"
}
}
endpoint_attachments = {
diff --git a/tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars b/tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars
new file mode 100644
index 000000000..2a9164bf4
--- /dev/null
+++ b/tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars
@@ -0,0 +1,13 @@
+project_id = "my-project"
+environments = {
+ apis-test = {
+ display_name = "APIs test"
+ description = "APIs Test"
+ api_proxy_type = "PROGRAMMABLE"
+ envgroups = ["test"]
+ node_config = {
+ min_node_count = 2
+ max_node_count = 5
+ }
+ }
+}
diff --git a/tests/modules/apigee/fixture/test.env_only_with_deployment_type.tfvars b/tests/modules/apigee/fixture/test.env_only_with_deployment_type.tfvars
new file mode 100644
index 000000000..48ef24e68
--- /dev/null
+++ b/tests/modules/apigee/fixture/test.env_only_with_deployment_type.tfvars
@@ -0,0 +1,13 @@
+project_id = "my-project"
+environments = {
+ apis-test = {
+ display_name = "APIs test"
+ description = "APIs Test"
+ deployment_type = "ARCHIVE"
+ envgroups = ["test"]
+ node_config = {
+ min_node_count = 2
+ max_node_count = 5
+ }
+ }
+}
diff --git a/tests/modules/apigee/fixture/test.instance_only.tfvars b/tests/modules/apigee/fixture/test.instance_only.tfvars
index 3d3eb1be1..d9399bfa9 100644
--- a/tests/modules/apigee/fixture/test.instance_only.tfvars
+++ b/tests/modules/apigee/fixture/test.instance_only.tfvars
@@ -1,8 +1,9 @@
project_id = "my-project"
instances = {
instance-test-ew1 = {
- region = "europe-west1"
- environments = ["apis-test"]
- psa_ip_cidr_range = "10.0.4.0/22"
+ region = "europe-west1"
+ environments = ["apis-test"]
+ runtime_ip_cidr_range = "10.0.4.0/22"
+ troubleshooting_ip_cidr_range = "10.1.1.0.0/28"
}
-}
\ No newline at end of file
+}
diff --git a/tests/modules/apigee/fixture/variables.tf b/tests/modules/apigee/fixture/variables.tf
index 266f0d34e..00961aac2 100644
--- a/tests/modules/apigee/fixture/variables.tf
+++ b/tests/modules/apigee/fixture/variables.tf
@@ -32,8 +32,10 @@ variable "envgroups" {
variable "environments" {
description = "Environments."
type = map(object({
- display_name = optional(string)
- description = optional(string, "Terraform-managed")
+ display_name = optional(string)
+ description = optional(string, "Terraform-managed")
+ deployment_type = optional(string)
+ api_proxy_type = optional(string)
node_config = optional(object({
min_node_count = optional(number)
max_node_count = optional(number)
@@ -47,13 +49,14 @@ variable "environments" {
variable "instances" {
description = "Instances."
type = map(object({
- display_name = optional(string)
- description = optional(string, "Terraform-managed")
- region = string
- environments = list(string)
- psa_ip_cidr_range = string
- disk_encryption_key = optional(string)
- consumer_accept_list = optional(list(string))
+ display_name = optional(string)
+ description = optional(string, "Terraform-managed")
+ region = string
+ environments = list(string)
+ runtime_ip_cidr_range = string
+ troubleshooting_ip_cidr_range = string
+ disk_encryption_key = optional(string)
+ consumer_accept_list = optional(list(string))
}))
default = null
}
diff --git a/tests/modules/apigee/test_plan.py b/tests/modules/apigee/test_plan.py
index e693ddbb2..d16ef2963 100644
--- a/tests/modules/apigee/test_plan.py
+++ b/tests/modules/apigee/test_plan.py
@@ -54,6 +54,18 @@ def test_env_only(plan_runner):
'google_apigee_envgroup_attachment.envgroup_attachments': 1,
}
+def test_env_only_with_deployment_type(plan_runner):
+ "Test that creates an environment in an existing environment group, with deployment_type set."
+ _, resources = plan_runner(tf_var_file='test.env_only_with_deployment_type.tfvars')
+ assert [r['values'].get('deployment_type') for r in resources
+ ] == [None, 'ARCHIVE']
+
+def test_env_only_with_api_proxy_type(plan_runner):
+ "Test that creates an environment in an existing environment group, with api_proxy_type set."
+ _, resources = plan_runner(tf_var_file='test.env_only_with_api_proxy_type.tfvars')
+ assert [r['values'].get('api_proxy_type') for r in resources
+ ] == [None, 'PROGRAMMABLE']
+
def test_instance_only(plan_runner):
"Test that creates only an instance."
_, resources = plan_runner(tf_var_file='test.instance_only.tfvars')
diff --git a/tests/modules/bigtable_instance/fixture/main.tf b/tests/modules/bigtable_instance/fixture/main.tf
index fa74a6c8e..4fa83ce2b 100644
--- a/tests/modules/bigtable_instance/fixture/main.tf
+++ b/tests/modules/bigtable_instance/fixture/main.tf
@@ -22,12 +22,15 @@ module "test" {
"roles/bigtable.user" = ["user:me@example.com"]
}
tables = {
- test-1 = null,
+ test-1 = {},
test-2 = {
- split_keys = ["a", "b", "c"]
- column_family = null
+ split_keys = ["a", "b", "c"]
}
}
- zone = var.zone
+ clusters = {
+ test = {
+ zone = var.zone
+ }
+ }
}
diff --git a/tests/modules/compute_mig/test_plan.py b/tests/modules/compute_mig/test_plan.py
index e24a7ca70..7fec3c1bd 100644
--- a/tests/modules/compute_mig/test_plan.py
+++ b/tests/modules/compute_mig/test_plan.py
@@ -84,7 +84,7 @@ def test_stateful_mig(plan_runner):
"Test stateful instances - mig."
stateful_disks = '''{
- persistent-disk-1 = null
+ persistent-disk-1 = false
}'''
_, resources = plan_runner(stateful_disks=stateful_disks)
assert len(resources) == 1
diff --git a/tests/modules/compute_vm/examples/alias-ips.yaml b/tests/modules/compute_vm/examples/alias-ips.yaml
new file mode 100644
index 000000000..016f96609
--- /dev/null
+++ b/tests/modules/compute_vm/examples/alias-ips.yaml
@@ -0,0 +1,36 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vm-with-alias-ips.google_compute_instance.default[0]:
+ name: test
+ network_interface:
+ - access_config: []
+ alias_ip_range:
+ - ip_cidr_range: 10.16.0.10/32
+ subnetwork_range_name: alias1
+ ipv6_access_config: []
+ network: projects/xxx/global/networks/aaa
+ nic_type: null
+ queue_count: null
+ subnetwork: subnet_self_link
+ project: my-project
+ zone: europe-west1-b
+
+counts:
+ google_compute_instance: 1
+ modules: 1
+ resources: 1
+
+outputs: {}
diff --git a/tests/modules/compute_vm/examples/cmek.yaml b/tests/modules/compute_vm/examples/cmek.yaml
new file mode 100644
index 000000000..cf390fde0
--- /dev/null
+++ b/tests/modules/compute_vm/examples/cmek.yaml
@@ -0,0 +1,57 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.kms-vm-example.google_compute_disk.disks["attached-disk"]:
+ disk_encryption_key:
+ - kms_key_self_link: kms_key_self_link
+ kms_key_service_account: null
+ raw_key: null
+ labels:
+ disk_name: attached-disk
+ disk_type: pd-balanced
+ name: kms-test-attached-disk
+ project: project-id
+ size: 10
+ type: pd-balanced
+ zone: europe-west1-b
+ module.kms-vm-example.google_compute_instance.default[0]:
+ attached_disk:
+ - device_name: attached-disk
+ disk_encryption_key_raw: null
+ mode: READ_WRITE
+ source: kms-test-attached-disk
+ boot_disk:
+ - auto_delete: true
+ disk_encryption_key_raw: null
+ initialize_params:
+ - image: projects/debian-cloud/global/images/family/debian-10
+ size: 10
+ type: pd-balanced
+ kms_key_self_link: kms_key_self_link
+ mode: READ_WRITE
+ name: kms-test
+ zone: europe-west1-b
+ module.kms-vm-example.google_service_account.service_account[0]:
+ account_id: tf-vm-kms-test
+ description: null
+ disabled: false
+ display_name: Terraform VM kms-test.
+ project: project-id
+ timeouts: null
+
+counts:
+ google_compute_disk: 1
+ google_compute_instance: 1
+ google_service_account: 1
diff --git a/tests/modules/compute_vm/examples/confidential.yaml b/tests/modules/compute_vm/examples/confidential.yaml
new file mode 100644
index 000000000..e842d4cb4
--- /dev/null
+++ b/tests/modules/compute_vm/examples/confidential.yaml
@@ -0,0 +1,31 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.template-confidential-example.google_compute_instance_template.default[0]:
+ confidential_instance_config:
+ - enable_confidential_compute: true
+ name_prefix: confidential-template-
+ project: project-id
+ region: europe-west1
+ module.vm-confidential-example.google_compute_instance.default[0]:
+ confidential_instance_config:
+ - enable_confidential_compute: true
+ name: confidential-vm
+ project: project-id
+ zone: europe-west1-b
+
+counts:
+ google_compute_instance: 1
+ google_compute_instance_template: 1
diff --git a/tests/modules/compute_vm/examples/disk-options.yaml b/tests/modules/compute_vm/examples/disk-options.yaml
new file mode 100644
index 000000000..91c11b419
--- /dev/null
+++ b/tests/modules/compute_vm/examples/disk-options.yaml
@@ -0,0 +1,59 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vm-disk-options-example.google_compute_disk.disks["data2"]:
+ name: test-data2
+ project: project-id
+ size: 20
+ snapshot: snapshot-2
+ type: pd-ssd
+ zone: europe-west1-b
+ module.vm-disk-options-example.google_compute_instance.default[0]:
+ attached_disk:
+ - device_name: data2
+ disk_encryption_key_raw: null
+ mode: READ_ONLY
+ source: test-data2
+ - device_name: data1
+ disk_encryption_key_raw: null
+ mode: READ_WRITE
+ source: test-data1
+ boot_disk:
+ - auto_delete: true
+ disk_encryption_key_raw: null
+ initialize_params:
+ - image: projects/debian-cloud/global/images/family/debian-11
+ size: 10
+ type: pd-balanced
+ mode: READ_WRITE
+ description: Managed by the compute-vm Terraform module.
+ name: test
+ project: project-id
+ zone: europe-west1-b
+ module.vm-disk-options-example.google_compute_region_disk.disks["data1"]:
+ name: test-data1
+ project: project-id
+ region: europe-west1
+ replica_zones:
+ - europe-west1-b
+ - europe-west1-c
+ size: 10
+ type: pd-balanced
+
+counts:
+ google_compute_disk: 1
+ google_compute_instance: 1
+ google_compute_region_disk: 1
+ google_service_account: 1
diff --git a/tests/modules/compute_vm/examples/group.yaml b/tests/modules/compute_vm/examples/group.yaml
new file mode 100644
index 000000000..c28c47648
--- /dev/null
+++ b/tests/modules/compute_vm/examples/group.yaml
@@ -0,0 +1,27 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.instance-group.google_compute_instance.default[0]: {}
+ module.instance-group.google_compute_instance_group.unmanaged[0]:
+ name: ilb-test
+ named_port: []
+ network: projects/xxx/global/networks/aaa
+ project: my-project
+ timeouts: null
+ zone: europe-west1-b
+
+counts:
+ google_compute_instance: 1
+ google_compute_instance_group: 1
diff --git a/tests/modules/compute_vm/examples/gvnic.yaml b/tests/modules/compute_vm/examples/gvnic.yaml
new file mode 100644
index 000000000..da95de9e4
--- /dev/null
+++ b/tests/modules/compute_vm/examples/gvnic.yaml
@@ -0,0 +1,43 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ google_compute_image.cos-gvnic:
+ guest_os_features:
+ - type: GVNIC
+ - type: SEV_CAPABLE
+ - type: UEFI_COMPATIBLE
+ - type: VIRTIO_SCSI_MULTIQUEUE
+ name: my-image
+ project: my-project
+ source_image: https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-89-16108-534-18
+ module.vm-with-gvnic.google_compute_instance.default[0]:
+ name: test
+ network_interface:
+ - access_config: []
+ alias_ip_range: []
+ ipv6_access_config: []
+ network: projects/xxx/global/networks/aaa
+ nic_type: GVNIC
+ queue_count: null
+ subnetwork: subnet_self_link
+ project: my-project
+ zone: europe-west1-b
+
+counts:
+ google_compute_image: 1
+ google_compute_instance: 1
+ google_service_account: 1
+ modules: 1
+ resources: 3
diff --git a/tests/modules/compute_vm/examples/iam.yaml b/tests/modules/compute_vm/examples/iam.yaml
new file mode 100644
index 000000000..254d266d7
--- /dev/null
+++ b/tests/modules/compute_vm/examples/iam.yaml
@@ -0,0 +1,34 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vm-iam-example.google_compute_instance.default[0]:
+ name: webserver
+ module.vm-iam-example.google_compute_instance_iam_binding.default["roles/compute.instanceAdmin"]:
+ condition: []
+ instance_name: webserver
+ members:
+ - group:admin@example.com
+ - group:webserver@example.com
+ project: project-id
+ role: roles/compute.instanceAdmin
+ zone: europe-west1-b
+
+counts:
+ google_compute_instance: 1
+ google_compute_instance_iam_binding: 1
+ modules: 1
+ resources: 2
+
+outputs: {}
diff --git a/tests/modules/compute_vm/examples/ips.yaml b/tests/modules/compute_vm/examples/ips.yaml
new file mode 100644
index 000000000..65931abb5
--- /dev/null
+++ b/tests/modules/compute_vm/examples/ips.yaml
@@ -0,0 +1,45 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vm-external-ip.google_compute_instance.default[0]:
+ name: vm-external-ip
+ network_interface:
+ - access_config:
+ - nat_ip: 8.8.8.8
+ public_ptr_domain_name: null
+ alias_ip_range: []
+ ipv6_access_config: []
+ network: projects/xxx/global/networks/aaa
+ nic_type: null
+ queue_count: null
+ subnetwork: subnet_self_link
+ project: my-project
+ zone: europe-west1-b
+ module.vm-internal-ip.google_compute_instance.default[0]:
+ name: vm-internal-ip
+ network_interface:
+ - access_config: []
+ alias_ip_range: []
+ ipv6_access_config: []
+ network: projects/xxx/global/networks/aaa
+ network_ip: 10.0.0.2
+ nic_type: null
+ queue_count: null
+ subnetwork: subnet_self_link
+ project: my-project
+ zone: europe-west1-b
+
+counts:
+ google_compute_instance: 2
diff --git a/tests/modules/net_vpc/simple.yaml b/tests/modules/compute_vm/examples/metadata.yaml
similarity index 54%
rename from tests/modules/net_vpc/simple.yaml
rename to tests/modules/compute_vm/examples/metadata.yaml
index 004be7ecf..fbe0d06ff 100644
--- a/tests/modules/net_vpc/simple.yaml
+++ b/tests/modules/compute_vm/examples/metadata.yaml
@@ -1,4 +1,4 @@
-# Copyright 2022 Google LLC
+# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,24 +13,20 @@
# limitations under the License.
values:
- google_compute_network.network[0]:
- auto_create_subnetworks: false
- delete_default_routes_on_create: false
- description: Terraform-managed.
- name: test
- project: test-project
- routing_mode: GLOBAL
+ module.vm-metadata-example.google_compute_instance.default[0]:
+ metadata:
+ startup-script: |
+ #! /bin/bash
+ apt-get update
+ apt-get install -y nginx
+ name: nginx-server
+ project: project-id
+ zone: europe-west1-b
+ labels:
+ env: dev
+ system: crm
+ module.vm-metadata-example.google_service_account.service_account[0]: {}
counts:
- google_compute_network: 1
-
-outputs:
- bindings: {}
- project_id: test-project
- subnet_ips: {}
- subnet_regions: {}
- subnet_secondary_ranges: {}
- subnet_self_links: {}
- subnets: {}
- subnets_proxy_only: {}
- subnets_psc: {}
+ google_compute_instance: 1
+ google_service_account: 1
diff --git a/tests/modules/compute_vm/examples/sas.yaml b/tests/modules/compute_vm/examples/sas.yaml
new file mode 100644
index 000000000..96a948317
--- /dev/null
+++ b/tests/modules/compute_vm/examples/sas.yaml
@@ -0,0 +1,49 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vm-default-sa-example2.google_compute_instance.default[0]:
+ name: test3
+ project: project-id
+ service_account:
+ - scopes:
+ - https://www.googleapis.com/auth/devstorage.read_only
+ - https://www.googleapis.com/auth/logging.write
+ - https://www.googleapis.com/auth/monitoring.write
+ zone: europe-west1-b
+ module.vm-managed-sa-example.google_compute_instance.default[0]:
+ name: test1
+ project: project-id
+ service_account:
+ - scopes:
+ - https://www.googleapis.com/auth/cloud-platform
+ - https://www.googleapis.com/auth/userinfo.email
+ zone: europe-west1-b
+ module.vm-managed-sa-example.google_service_account.service_account[0]:
+ account_id: tf-vm-test1
+ display_name: Terraform VM test1.
+ project: project-id
+ module.vm-managed-sa-example2.google_compute_instance.default[0]:
+ name: test2
+ project: project-id
+ service_account:
+ - scopes:
+ - https://www.googleapis.com/auth/cloud-platform
+ zone: europe-west1-b
+
+counts:
+ google_compute_instance: 3
+ google_service_account: 1
+ modules: 3
+ resources: 4
diff --git a/tests/modules/compute_vm/examples/simple.yaml b/tests/modules/compute_vm/examples/simple.yaml
new file mode 100644
index 000000000..6754efaae
--- /dev/null
+++ b/tests/modules/compute_vm/examples/simple.yaml
@@ -0,0 +1,72 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.simple-vm-example.google_compute_instance.default[0]:
+ advanced_machine_features: []
+ allow_stopping_for_update: true
+ attached_disk: []
+ boot_disk:
+ - auto_delete: true
+ disk_encryption_key_raw: null
+ initialize_params:
+ - image: projects/debian-cloud/global/images/family/debian-11
+ size: 10
+ type: pd-balanced
+ mode: READ_WRITE
+ can_ip_forward: false
+ deletion_protection: false
+ description: Managed by the compute-vm Terraform module.
+ enable_display: false
+ hostname: null
+ labels: null
+ machine_type: f1-micro
+ metadata: null
+ metadata_startup_script: null
+ name: test
+ network_interface:
+ - access_config: []
+ alias_ip_range: []
+ ipv6_access_config: []
+ network: projects/xxx/global/networks/aaa
+ nic_type: null
+ queue_count: null
+ subnetwork: subnet_self_link
+ project: project-id
+ scheduling:
+ - automatic_restart: true
+ instance_termination_action: null
+ max_run_duration: []
+ min_node_cpus: null
+ node_affinities: []
+ on_host_maintenance: MIGRATE
+ preemptible: false
+ provisioning_model: STANDARD
+ scratch_disk: []
+ service_account:
+ - scopes:
+ - https://www.googleapis.com/auth/cloud-platform
+ - https://www.googleapis.com/auth/userinfo.email
+ shielded_instance_config: []
+ tags: null
+ zone: europe-west1-b
+ module.simple-vm-example.google_service_account.service_account[0]:
+ account_id: tf-vm-test
+ display_name: Terraform VM test.
+ project: project-id
+
+
+counts:
+ google_compute_instance: 1
+ google_service_account: 1
diff --git a/tests/modules/organization/iam_additive.yaml b/tests/modules/compute_vm/examples/spot.yaml
similarity index 56%
rename from tests/modules/organization/iam_additive.yaml
rename to tests/modules/compute_vm/examples/spot.yaml
index 68eda8c27..c15852dbc 100644
--- a/tests/modules/organization/iam_additive.yaml
+++ b/tests/modules/compute_vm/examples/spot.yaml
@@ -1,4 +1,4 @@
-# Copyright 2022 Google LLC
+# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,19 +13,19 @@
# limitations under the License.
values:
- google_organization_iam_binding.authoritative["user:one@example.org"]:
- condition: []
- members:
- - roles/owner
- org_id: '1234567890'
- role: user:one@example.org
- google_organization_iam_binding.authoritative["user:two@example.org"]:
- condition: []
- members:
- - roles/editor
- - roles/owner
- org_id: '1234567890'
- role: user:two@example.org
+ module.spot-vm-example.google_compute_instance.default[0]:
+ name: test
+ project: project-id
+ scheduling:
+ - automatic_restart: false
+ instance_termination_action: STOP
+ max_run_duration: []
+ min_node_cpus: null
+ node_affinities: []
+ on_host_maintenance: TERMINATE
+ preemptible: true
+ provisioning_model: SPOT
+ zone: europe-west1-b
counts:
- google_organization_iam_binding: 2
+ google_compute_instance: 1
diff --git a/tests/modules/compute_vm/examples/template.yaml b/tests/modules/compute_vm/examples/template.yaml
new file mode 100644
index 000000000..1f1888bfc
--- /dev/null
+++ b/tests/modules/compute_vm/examples/template.yaml
@@ -0,0 +1,65 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.cos-test.google_compute_instance_template.default[0]:
+ disk:
+ - auto_delete: true
+ boot: true
+ disk_encryption_key: []
+ disk_name: null
+ disk_size_gb: 10
+ disk_type: pd-balanced
+ labels: null
+ resource_policies: null
+ source: null
+ source_image: projects/cos-cloud/global/images/family/cos-stable
+ source_image_encryption_key: []
+ source_snapshot: null
+ source_snapshot_encryption_key: []
+ - auto_delete: true
+ device_name: disk-1
+ disk_encryption_key: []
+ disk_name: disk-1
+ disk_size_gb: 10
+ disk_type: pd-balanced
+ labels: null
+ mode: READ_WRITE
+ resource_policies: null
+ source: null
+ source_image_encryption_key: []
+ source_snapshot: null
+ source_snapshot_encryption_key: []
+ type: PERSISTENT
+ name_prefix: test-
+ network_interface:
+ - access_config: []
+ alias_ip_range: []
+ ipv6_access_config: []
+ network: projects/xxx/global/networks/aaa
+ network_ip: null
+ nic_type: null
+ queue_count: null
+ subnetwork: subnet_self_link
+ project: my-project
+ region: europe-west1
+ service_account:
+ - email: vm-default@my-project.iam.gserviceaccount.com
+ scopes:
+ - https://www.googleapis.com/auth/devstorage.read_only
+ - https://www.googleapis.com/auth/logging.write
+ - https://www.googleapis.com/auth/monitoring.write
+
+counts:
+ google_compute_instance_template: 1
diff --git a/tests/modules/compute_vm/fixture/main.tf b/tests/modules/compute_vm/fixture/main.tf
deleted file mode 100644
index 5815f25f7..000000000
--- a/tests/modules/compute_vm/fixture/main.tf
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * 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 "test" {
- source = "../../../../modules/compute-vm"
- project_id = "my-project"
- zone = "europe-west1-b"
- name = "test"
- attached_disks = var.attached_disks
- attached_disk_defaults = var.attached_disk_defaults
- create_template = var.create_template
- confidential_compute = var.confidential_compute
- group = var.group
- iam = var.iam
- metadata = var.metadata
- network_interfaces = var.network_interfaces
- service_account_create = var.service_account_create
-}
diff --git a/tests/modules/compute_vm/fixture/variables.tf b/tests/modules/compute_vm/fixture/variables.tf
deleted file mode 100644
index 02d839f64..000000000
--- a/tests/modules/compute_vm/fixture/variables.tf
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * 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 "attached_disks" {
- description = "Additional disks, if options is null defaults will be used in its place. Source type is one of 'image' (zonal disks in vms and template), 'snapshot' (vm), 'existing', and null."
- type = any
- default = []
-}
-
-variable "attached_disk_defaults" {
- description = "Defaults for attached disks options."
- type = any
- default = {
- auto_delete = true
- mode = "READ_WRITE"
- replica_zone = null
- type = "pd-balanced"
- }
-}
-
-variable "confidential_compute" {
- type = bool
- default = false
-}
-
-variable "create_template" {
- type = bool
- default = false
-}
-
-variable "group" {
- type = any
- default = null
-}
-
-variable "iam" {
- type = map(set(string))
- default = {}
-}
-
-variable "metadata" {
- type = map(string)
- default = {}
-}
-
-variable "network_interfaces" {
- type = any
- default = [{
- network = "https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default",
- subnetwork = "https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west1/subnetworks/default-default",
- }]
-}
-
-variable "service_account_create" {
- type = bool
- default = false
-}
diff --git a/tests/modules/compute_vm/test_plan.py b/tests/modules/compute_vm/test_plan.py
deleted file mode 100644
index 701891c5b..000000000
--- a/tests/modules/compute_vm/test_plan.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# 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_defaults(plan_runner):
- _, resources = plan_runner()
- assert len(resources) == 1
- assert resources[0]['type'] == 'google_compute_instance'
-
-
-def test_service_account(plan_runner):
- _, resources = plan_runner(service_account_create='true')
- assert len(resources) == 2
- assert set(r['type'] for r in resources) == set([
- 'google_compute_instance', 'google_service_account'
- ])
-
-
-def test_template(plan_runner):
- _, resources = plan_runner(create_template='true')
- assert len(resources) == 1
- assert resources[0]['type'] == 'google_compute_instance_template'
- assert resources[0]['values']['name_prefix'] == 'test-'
-
-
-def test_group(plan_runner):
- _, resources = plan_runner(group='{named_ports={}}')
- assert len(resources) == 2
- assert set(r['type'] for r in resources) == set([
- 'google_compute_instance_group', 'google_compute_instance'
- ])
-
-
-def test_iam(plan_runner):
- iam = (
- '{"roles/compute.instanceAdmin" = ["user:a@a.com", "user:b@a.com"],'
- '"roles/iam.serviceAccountUser" = ["user:a@a.com"]}'
- )
- _, resources = plan_runner(iam=iam)
- assert len(resources) == 3
- assert set(r['type'] for r in resources) == set([
- 'google_compute_instance', 'google_compute_instance_iam_binding'])
- iam_bindings = dict(
- (r['index'], r['values']['members']) for r in resources if r['type']
- == 'google_compute_instance_iam_binding'
- )
- assert iam_bindings == {
- 'roles/compute.instanceAdmin': ['user:a@a.com', 'user:b@a.com'],
- 'roles/iam.serviceAccountUser': ['user:a@a.com'],
- }
-
-
-def test_confidential_compute(plan_runner):
- _, resources = plan_runner(confidential_compute='true')
- assert len(resources) == 1
- assert resources[0]['values']['confidential_instance_config'] == [
- {'enable_confidential_compute': True}]
- assert resources[0]['values']['scheduling'][0]['on_host_maintenance'] == 'TERMINATE'
-
-
-def test_confidential_compute_template(plan_runner):
- _, resources = plan_runner(confidential_compute='true',
- create_template='true')
- assert len(resources) == 1
- assert resources[0]['values']['confidential_instance_config'] == [
- {'enable_confidential_compute': True}]
- assert resources[0]['values']['scheduling'][0]['on_host_maintenance'] == 'TERMINATE'
diff --git a/tests/modules/compute_vm/test_plan_disks.py b/tests/modules/compute_vm/test_plan_disks.py
deleted file mode 100644
index 153c072f5..000000000
--- a/tests/modules/compute_vm/test_plan_disks.py
+++ /dev/null
@@ -1,165 +0,0 @@
-# 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_types(plan_runner):
- _disks = '''[{
- name = "data1"
- size = "10"
- source_type = "image"
- source = "image-1"
- options = null
- }, {
- name = "data2"
- size = "20"
- source_type = "snapshot"
- source = "snapshot-2"
- options = null
- }, {
- name = "data3"
- size = null
- source_type = "attach"
- source = "disk-3"
- options = null
- }]
- '''
- _, resources = plan_runner(attached_disks=_disks)
- assert len(resources) == 3
- disks = {
- r['values']['name']: r['values']
- for r in resources if r['type'] == 'google_compute_disk'
- }
- assert disks['test-data1']['size'] == 10
- assert disks['test-data2']['size'] == 20
- assert disks['test-data1']['image'] == 'image-1'
- assert disks['test-data1']['snapshot'] is None
- assert disks['test-data2']['snapshot'] == 'snapshot-2'
- assert disks['test-data2']['image'] is None
- instance = [
- r['values'] for r in resources
- if r['type'] == 'google_compute_instance'
- ][0]
- instance_disks = {
- d['source']: d['device_name']
- for d in instance['attached_disk']
- }
- assert instance_disks == {
- 'test-data1': 'data1',
- 'test-data2': 'data2',
- 'disk-3': 'data3'
- }
-
-
-def test_options(plan_runner):
- _disks = '''[{
- name = "data1"
- size = "10"
- source_type = "image"
- source = "image-1"
- options = {
- mode = null, replica_zone = null, type = "pd-standard"
- }
- }, {
- name = "data2"
- size = "20"
- source_type = null
- source = null
- options = {
- mode = null, replica_zone = "europe-west1-c", type = "pd-ssd"
- }
- }]
- '''
- _, resources = plan_runner(attached_disks=_disks)
- assert len(resources) == 3
- disks_z = [
- r['values'] for r in resources if r['type'] == 'google_compute_disk'
- ]
- disks_r = [
- r['values'] for r in resources
- if r['type'] == 'google_compute_region_disk'
- ]
- assert len(disks_z) == len(disks_r) == 1
- instance = [
- r['values'] for r in resources
- if r['type'] == 'google_compute_instance'
- ][0]
- instance_disks = [d['device_name'] for d in instance['attached_disk']]
- assert instance_disks == ['data1', 'data2']
-
-
-def test_template(plan_runner):
- _disks = '''[{
- name = "data1"
- size = "10"
- source_type = "image"
- source = "image-1"
- options = {
- mode = null, replica_zone = null, type = "pd-standard"
- }
- }, {
- name = "data2"
- size = "20"
- source_type = null
- source = null
- options = {
- mode = null, replica_zone = "europe-west1-c", type = "pd-ssd"
- }
- }]
- '''
- _, resources = plan_runner(attached_disks=_disks, create_template="true")
- assert len(resources) == 1
- template = [
- r['values'] for r in resources
- if r['type'] == 'google_compute_instance_template'
- ][0]
- assert len(template['disk']) == 3
-
-
-def test_auto_delete(plan_runner):
- _disks = '''[{
- name = "data1"
- size = "10"
- options = {
- auto_delete = true, mode = "READ_WRITE"
- }
- }, {
- name = "data2"
- size = "20"
- options = {
- auto_delete = false, mode = "READ_WRITE"
- },
- }, {
- name = "data3"
- size = "20"
- options = {
- mode = "READ_ONLY"
- }
- }]
- '''
- _, resources = plan_runner(attached_disks=_disks, create_template="true")
- assert len(resources) == 1
- template = [
- r['values'] for r in resources
- if r['type'] == 'google_compute_instance_template'
- ][0]
- additional_disks = [
- d for d in template['disk'] if 'boot' not in d or d['boot'] != True
- ]
- assert len(additional_disks) == 3
- disk_data1 = [d for d in additional_disks if d['disk_name'] == 'data1']
- disk_data2 = [d for d in additional_disks if d['disk_name'] == 'data2']
- disk_data3 = [d for d in additional_disks if d['disk_name'] == 'data3']
- assert len(disk_data1) == 1 and disk_data1[0]['auto_delete'] == True
- assert len(disk_data2) == 1 and disk_data2[0]['auto_delete'] == False
- assert len(disk_data3) == 1 and disk_data3[0]['auto_delete'] == False
diff --git a/tests/modules/compute_vm/test_plan_interfaces.py b/tests/modules/compute_vm/test_plan_interfaces.py
deleted file mode 100644
index e88c087be..000000000
--- a/tests/modules/compute_vm/test_plan_interfaces.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# 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_address(plan_runner):
- nics = '''[{
- network = "https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default",
- subnetwork = "https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west1/subnetworks/default-default",
- nat = false,
- addresses = {external=null, internal="10.0.0.2"}
- }]
- '''
- _, resources = plan_runner(network_interfaces=nics)
- assert len(resources) == 1
- n = resources[0]['values']['network_interface'][0]
- assert n['network_ip'] == "10.0.0.2"
- assert n['access_config'] == []
-
-
-def test_nat_address(plan_runner):
- nics = '''[{
- network = "https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default",
- subnetwork = "https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west1/subnetworks/default-default",
- nat = true,
- addresses = {external="8.8.8.8", internal=null}
- }]
- '''
- _, resources = plan_runner(network_interfaces=nics)
- assert len(resources) == 1
- n = resources[0]['values']['network_interface'][0]
- assert 'network_ip' not in n
- assert n['access_config'][0]['nat_ip'] == '8.8.8.8'
diff --git a/tests/modules/organization/logging_exclusions.yaml b/tests/modules/dns/examples/forwarding-zone.yaml
similarity index 52%
rename from tests/modules/organization/logging_exclusions.yaml
rename to tests/modules/dns/examples/forwarding-zone.yaml
index 4d51dd7cd..4a09114ee 100644
--- a/tests/modules/organization/logging_exclusions.yaml
+++ b/tests/modules/dns/examples/forwarding-zone.yaml
@@ -1,4 +1,4 @@
-# Copyright 2022 Google LLC
+# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,18 +13,22 @@
# limitations under the License.
values:
- google_logging_organization_exclusion.logging-exclusion["exclusion1"]:
- description: exclusion1 (Terraform-managed).
- disabled: null
- filter: resource.type=gce_instance
- name: exclusion1
- org_id: '1234567890'
- google_logging_organization_exclusion.logging-exclusion["exclusion2"]:
- description: exclusion2 (Terraform-managed).
- disabled: null
- filter: severity=NOTICE
- name: exclusion2
- org_id: '1234567890'
+ module.private-dns.google_dns_managed_zone.non-public[0]:
+ dns_name: test.example.
+ forwarding_config:
+ - target_name_servers:
+ - forwarding_path: ''
+ ipv4_address: 10.0.1.1
+ - forwarding_path: private
+ ipv4_address: 1.2.3.4
+ name: test-example
+ private_visibility_config:
+ - gke_clusters: []
+ networks:
+ - network_url: projects/xxx/global/networks/aaa
+ project: myproject
+ visibility: private
counts:
- google_logging_organization_exclusion: 2
+ google_dns_managed_zone: 1
+
diff --git a/tests/modules/dns/examples/peering-zone.yaml b/tests/modules/dns/examples/peering-zone.yaml
new file mode 100644
index 000000000..9f16adab6
--- /dev/null
+++ b/tests/modules/dns/examples/peering-zone.yaml
@@ -0,0 +1,34 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.private-dns.google_dns_managed_zone.non-public[0]:
+ description: Forwarding zone for .
+ dns_name: .
+ forwarding_config: []
+ name: test-example
+ peering_config:
+ - target_network:
+ - network_url: projects/xxx/global/networks/ccc
+ private_visibility_config:
+ - gke_clusters: []
+ networks:
+ - network_url: projects/xxx/global/networks/aaa
+ project: myproject
+ visibility: private
+
+counts:
+ google_dns_managed_zone: 1
+
+outputs: {}
diff --git a/tests/modules/dns/examples/private-zone.yaml b/tests/modules/dns/examples/private-zone.yaml
new file mode 100644
index 000000000..f64266450
--- /dev/null
+++ b/tests/modules/dns/examples/private-zone.yaml
@@ -0,0 +1,50 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.private-dns.google_dns_managed_zone.non-public[0]:
+ description: Terraform managed.
+ dns_name: test.example.
+ force_destroy: false
+ forwarding_config: []
+ name: test-example
+ peering_config: []
+ private_visibility_config:
+ - gke_clusters: []
+ networks:
+ - network_url: projects/xxx/global/networks/aaa
+ project: myproject
+ visibility: private
+ module.private-dns.google_dns_record_set.cloud-static-records["A localhost"]:
+ managed_zone: test-example
+ name: localhost.test.example.
+ project: myproject
+ routing_policy: []
+ rrdatas:
+ - 127.0.0.1
+ ttl: 300
+ type: A
+ module.private-dns.google_dns_record_set.cloud-static-records["A myhost"]:
+ managed_zone: test-example
+ name: myhost.test.example.
+ project: myproject
+ routing_policy: []
+ rrdatas:
+ - 10.0.0.120
+ ttl: 600
+ type: A
+
+counts:
+ google_dns_managed_zone: 1
+ google_dns_record_set: 2
diff --git a/tests/modules/dns/examples/public-zone.yaml b/tests/modules/dns/examples/public-zone.yaml
new file mode 100644
index 000000000..0f8067a76
--- /dev/null
+++ b/tests/modules/dns/examples/public-zone.yaml
@@ -0,0 +1,38 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.public-dns.google_dns_managed_zone.public[0]:
+ dns_name: example.com.
+ name: example
+ project: myproject
+ visibility: public
+ module.public-dns.google_dns_record_set.cloud-static-records["A myhost"]:
+ managed_zone: example
+ name: myhost.example.com.
+ project: myproject
+ routing_policy: []
+ rrdatas:
+ - 127.0.0.1
+ ttl: 300
+ type: A
+
+counts:
+ google_dns_keys: 1
+ google_dns_managed_zone: 1
+ google_dns_record_set: 1
+ modules: 1
+ resources: 3
+
+outputs: {}
diff --git a/tests/blueprints/cloud_operations/apigee/test_plan.py b/tests/modules/dns/examples/reverse-zone.yaml
similarity index 63%
rename from tests/blueprints/cloud_operations/apigee/test_plan.py
rename to tests/modules/dns/examples/reverse-zone.yaml
index 34ad83a86..17e76a12c 100644
--- a/tests/blueprints/cloud_operations/apigee/test_plan.py
+++ b/tests/modules/dns/examples/reverse-zone.yaml
@@ -1,4 +1,4 @@
-# Copyright 2022 Google LLC
+# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,10 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import collections
+values:
+ module.private-dns.google_dns_managed_zone.non-public[0]:
+ description: Terraform managed.
+ dns_name: 0.0.10.in-addr.arpa.
+ name: test-example
+ project: myproject
+ reverse_lookup: true
+ visibility: private
-def test_blueprint(recursive_e2e_plan_runner):
- "Test that all blueprint resources are created."
- count_modules, count_resources = recursive_e2e_plan_runner(tf_var_file='test.regular.tfvars')
- assert count_modules == 10
- assert count_resources == 59
+counts:
+ google_dns_managed_zone: 1
+
+outputs: {}
diff --git a/tests/modules/dns/examples/routing-policies.yaml b/tests/modules/dns/examples/routing-policies.yaml
new file mode 100644
index 000000000..45b19276c
--- /dev/null
+++ b/tests/modules/dns/examples/routing-policies.yaml
@@ -0,0 +1,80 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.private-dns.google_dns_managed_zone.non-public[0]:
+ dns_name: test.example.
+ name: test-example
+ project: myproject
+ module.private-dns.google_dns_record_set.cloud-geo-records["A geo"]:
+ managed_zone: test-example
+ name: geo.test.example.
+ project: myproject
+ routing_policy:
+ - enable_geo_fencing: null
+ geo:
+ - health_checked_targets: []
+ location: europe-west1
+ rrdatas:
+ - 10.0.0.1
+ - health_checked_targets: []
+ location: europe-west2
+ rrdatas:
+ - 10.0.0.2
+ - health_checked_targets: []
+ location: europe-west3
+ rrdatas:
+ - 10.0.0.3
+ primary_backup: []
+ wrr: []
+ rrdatas: null
+ ttl: 300
+ type: A
+ module.private-dns.google_dns_record_set.cloud-static-records["A regular"]:
+ managed_zone: test-example
+ name: regular.test.example.
+ project: myproject
+ routing_policy: []
+ rrdatas:
+ - 10.20.0.1
+ ttl: 300
+ type: A
+ module.private-dns.google_dns_record_set.cloud-wrr-records["A wrr"]:
+ managed_zone: test-example
+ name: wrr.test.example.
+ project: myproject
+ routing_policy:
+ - enable_geo_fencing: null
+ geo: []
+ primary_backup: []
+ wrr:
+ - health_checked_targets: []
+ rrdatas:
+ - 10.10.0.1
+ weight: 0.6
+ - health_checked_targets: []
+ rrdatas:
+ - 10.10.0.2
+ weight: 0.2
+ - health_checked_targets: []
+ rrdatas:
+ - 10.10.0.3
+ weight: 0.2
+ rrdatas: null
+ ttl: 600
+ type: A
+
+counts:
+ google_dns_managed_zone: 1
+ google_dns_record_set: 3
diff --git a/tests/modules/dns/fixture/main.tf b/tests/modules/dns/fixture/main.tf
deleted file mode 100644
index bab319204..000000000
--- a/tests/modules/dns/fixture/main.tf
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * 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 "test" {
- source = "../../../../modules/dns"
- project_id = "my-project"
- name = "test"
- domain = "test.example."
- client_networks = var.client_networks
- type = var.type
- forwarders = var.forwarders
- peer_network = var.peer_network
- recordsets = var.recordsets
-}
diff --git a/tests/modules/dns/fixture/variables.tf b/tests/modules/dns/fixture/variables.tf
deleted file mode 100644
index 8e55a287a..000000000
--- a/tests/modules/dns/fixture/variables.tf
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * 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 "client_networks" {
- type = list(string)
- default = [
- "https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default"
- ]
-}
-
-variable "forwarders" {
- type = map(string)
- default = {}
-}
-
-variable "peer_network" {
- type = string
- default = null
-}
-
-variable "recordsets" {
- type = any
- default = {
- "A localhost" = { ttl = 300, records = ["127.0.0.1"] }
- "A local-host.test.example." = { ttl = 300, records = ["127.0.0.2"] }
- "CNAME *" = { ttl = 300, records = ["localhost.example.org."] }
- "A " = { ttl = 300, records = ["127.0.0.3"] }
- "A geo" = {
- geo_routing = [
- { location = "europe-west1", records = ["127.0.0.4"] },
- { location = "europe-west2", records = ["127.0.0.5"] },
- { location = "europe-west3", records = ["127.0.0.6"] }
- ]
- }
- "A wrr" = {
- ttl = 600
- wrr_routing = [
- { weight = 0.6, records = ["127.0.0.7"] },
- { weight = 0.2, records = ["127.0.0.8"] },
- { weight = 0.2, records = ["127.0.0.9"] }
- ]
- }
- }
-}
-
-variable "type" {
- type = string
- default = "private"
-}
diff --git a/tests/modules/dns/no_clients.tfvars b/tests/modules/dns/no_clients.tfvars
new file mode 100644
index 000000000..97b722734
--- /dev/null
+++ b/tests/modules/dns/no_clients.tfvars
@@ -0,0 +1,5 @@
+type = "private"
+domain = "test.example."
+name = "test"
+project_id = "my-project"
+client_networks = []
diff --git a/tests/modules/dns/no_clients.yaml b/tests/modules/dns/no_clients.yaml
new file mode 100644
index 000000000..42f628c9c
--- /dev/null
+++ b/tests/modules/dns/no_clients.yaml
@@ -0,0 +1,25 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ google_dns_managed_zone.non-public[0]:
+ dns_name: test.example.
+ name: test
+ private_visibility_config: []
+ visibility: private
+
+counts:
+ google_dns_managed_zone: 1
+ modules: 0
+ resources: 1
diff --git a/tests/modules/dns/null_forwarders.tfvars b/tests/modules/dns/null_forwarders.tfvars
new file mode 100644
index 000000000..4514d6395
--- /dev/null
+++ b/tests/modules/dns/null_forwarders.tfvars
@@ -0,0 +1,4 @@
+type = "forwarding"
+domain = "test.example."
+name = "test"
+project_id = "my-project"
diff --git a/tests/modules/dns/null_forwarders.yaml b/tests/modules/dns/null_forwarders.yaml
new file mode 100644
index 000000000..bbe637fc2
--- /dev/null
+++ b/tests/modules/dns/null_forwarders.yaml
@@ -0,0 +1,20 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ google_dns_managed_zone.non-public[0]:
+ forwarding_config: []
+
+counts:
+ google_dns_managed_zone: 1
diff --git a/tests/modules/dns/test_plan.py b/tests/modules/dns/test_plan.py
deleted file mode 100644
index 5cc1ba709..000000000
--- a/tests/modules/dns/test_plan.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# 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_private(plan_runner):
- "Test private zone with three recordsets."
- _, resources = plan_runner()
- assert len(resources) == 7
- assert set(r['type'] for r in resources) == {
- 'google_dns_record_set', 'google_dns_managed_zone'
- }
- for r in resources:
- if r['type'] != 'google_dns_managed_zone':
- continue
- assert r['values']['visibility'] == 'private'
- assert len(r['values']['private_visibility_config']) == 1
-
-
-def test_private_recordsets(plan_runner):
- "Test recordsets in private zone."
- _, resources = plan_runner()
- recordsets = [
- r['values'] for r in resources if r['type'] == 'google_dns_record_set'
- ]
-
- assert set(r['name'] for r in recordsets) == {
- 'localhost.test.example.', 'local-host.test.example.', '*.test.example.',
- "test.example.", "geo.test.example.", "wrr.test.example."
- }
-
- for r in recordsets:
- if r['name'] not in ['wrr.test.example.', 'geo.test.example.']:
- assert r['routing_policy'] == []
- assert r['rrdatas'] != []
-
-
-def test_routing_policies(plan_runner):
- "Test recordsets with routing policies."
- _, resources = plan_runner()
- recordsets = [
- r['values'] for r in resources if r['type'] == 'google_dns_record_set'
- ]
- geo_zone = [
- r['values'] for r in resources if r['address'] ==
- 'module.test.google_dns_record_set.cloud-geo-records["A geo"]'
- ][0]
- assert geo_zone['name'] == 'geo.test.example.'
- assert geo_zone['routing_policy'][0]['wrr'] == []
- geo_policy = geo_zone['routing_policy'][0]['geo']
- assert geo_policy[0]['location'] == 'europe-west1'
- assert geo_policy[0]['rrdatas'] == ['127.0.0.4']
- assert geo_policy[1]['location'] == 'europe-west2'
- assert geo_policy[1]['rrdatas'] == ['127.0.0.5']
- assert geo_policy[2]['location'] == 'europe-west3'
- assert geo_policy[2]['rrdatas'] == ['127.0.0.6']
-
- wrr_zone = [
- r['values'] for r in resources if r['address'] ==
- 'module.test.google_dns_record_set.cloud-wrr-records["A wrr"]'
- ][0]
- assert wrr_zone['name'] == 'wrr.test.example.'
- wrr_policy = wrr_zone['routing_policy'][0]['wrr']
- assert wrr_policy[0]['weight'] == 0.6
- assert wrr_policy[0]['rrdatas'] == ['127.0.0.7']
- assert wrr_policy[1]['weight'] == 0.2
- assert wrr_policy[1]['rrdatas'] == ['127.0.0.8']
- assert wrr_policy[2]['weight'] == 0.2
- assert wrr_policy[2]['rrdatas'] == ['127.0.0.9']
- assert wrr_zone['routing_policy'][0]['geo'] == []
-
-
-def test_private_no_networks(plan_runner):
- "Test private zone not exposed to any network."
- _, resources = plan_runner(client_networks='[]')
- for r in resources:
- if r['type'] != 'google_dns_managed_zone':
- continue
- assert r['values']['visibility'] == 'private'
- assert len(r['values']['private_visibility_config']) == 0
-
-
-def test_forwarding_recordsets_null_forwarders(plan_runner):
- "Test forwarding zone with wrong set of attributes does not break."
- _, resources = plan_runner(type='forwarding')
- assert len(resources) == 1
- resource = resources[0]
- assert resource['type'] == 'google_dns_managed_zone'
- assert resource['values']['forwarding_config'] == []
-
-
-def test_forwarding(plan_runner):
- "Test forwarding zone with single forwarder."
- _, resources = plan_runner(type='forwarding', recordsets='null',
- forwarders='{ "1.2.3.4" = null }')
- assert len(resources) == 1
- resource = resources[0]
- assert resource['type'] == 'google_dns_managed_zone'
- assert resource['values']['forwarding_config'] == [{
- 'target_name_servers': [{
- 'forwarding_path': '',
- 'ipv4_address': '1.2.3.4'
- }]
- }]
-
-
-def test_peering(plan_runner):
- "Test peering zone."
- _, resources = plan_runner(type='peering', recordsets='null',
- peer_network='dummy-vpc-self-link')
- assert len(resources) == 1
- resource = resources[0]
- assert resource['type'] == 'google_dns_managed_zone'
- assert resource['values']['peering_config'] == [{
- 'target_network': [{
- 'network_url': 'dummy-vpc-self-link'
- }]
- }]
-
-
-def test_public(plan_runner):
- "Test public zone with two recordsets."
- _, resources = plan_runner(type='public')
- for r in resources:
- if r['type'] != 'google_dns_managed_zone':
- continue
- assert r['values']['visibility'] == 'public'
- assert r['values']['private_visibility_config'] == []
diff --git a/tests/modules/dns/tftest.yaml b/tests/modules/dns/tftest.yaml
new file mode 100644
index 000000000..5172a013b
--- /dev/null
+++ b/tests/modules/dns/tftest.yaml
@@ -0,0 +1,19 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module: modules/dns
+
+tests:
+ no_clients:
+ null_forwarders:
diff --git a/tests/modules/folder/common.tfvars b/tests/modules/folder/common.tfvars
new file mode 100644
index 000000000..aebc74a01
--- /dev/null
+++ b/tests/modules/folder/common.tfvars
@@ -0,0 +1,2 @@
+parent = "organizations/12345678"
+name = "folder-a"
diff --git a/tests/modules/organization/firewall_policies.yaml b/tests/modules/folder/examples/hfw.yaml
similarity index 51%
rename from tests/modules/organization/firewall_policies.yaml
rename to tests/modules/folder/examples/hfw.yaml
index 4ecc5c72c..57abe480e 100644
--- a/tests/modules/organization/firewall_policies.yaml
+++ b/tests/modules/folder/examples/hfw.yaml
@@ -13,14 +13,31 @@
# limitations under the License.
values:
- google_compute_firewall_policy.policy["policy1"]:
- parent: organizations/1234567890
- short_name: policy1
- google_compute_firewall_policy.policy["policy2"]:
- parent: organizations/1234567890
- short_name: policy2
- google_compute_firewall_policy_rule.rule["policy1-allow-ingress"]:
+ module.folder1.google_compute_firewall_policy.policy["iap-policy"]:
+ description: null
+ short_name: iap-policy
+ module.folder1.google_compute_firewall_policy_association.association["iap-policy"]: {}
+ module.folder1.google_compute_firewall_policy_rule.rule["iap-policy-allow-admins"]:
action: allow
+ description: Access from the admin subnet to all subnets
+ direction: INGRESS
+ disabled: null
+ enable_logging: false
+ match:
+ - dest_ip_ranges: null
+ layer4_configs:
+ - ip_protocol: all
+ ports: []
+ src_ip_ranges:
+ - 10.0.0.0/8
+ - 172.16.0.0/12
+ - 192.168.0.0/16
+ priority: 1000
+ target_resources: null
+ target_service_accounts: null
+ module.folder1.google_compute_firewall_policy_rule.rule["iap-policy-allow-iap-ssh"]:
+ action: allow
+ description: Always allow ssh from IAP
direction: INGRESS
disabled: null
enable_logging: false
@@ -31,43 +48,20 @@ values:
ports:
- '22'
src_ip_ranges:
- - 10.0.0.0/8
- priority: 100
- target_resources: null
- target_service_accounts: null
- google_compute_firewall_policy_rule.rule["policy1-deny-egress"]:
- action: deny
- direction: EGRESS
- disabled: null
- enable_logging: false
- match:
- - dest_ip_ranges:
- - 192.168.0.0/24
- layer4_configs:
- - ip_protocol: tcp
- ports:
- - '443'
- src_ip_ranges: null
- priority: 200
- target_resources: null
- target_service_accounts: null
- google_compute_firewall_policy_rule.rule["policy2-allow-ingress"]:
- action: allow
- direction: INGRESS
- disabled: null
- enable_logging: false
- match:
- - dest_ip_ranges: null
- layer4_configs:
- - ip_protocol: tcp
- ports:
- - '22'
- src_ip_ranges:
- - 10.0.0.0/8
+ - 35.235.240.0/20
priority: 100
target_resources: null
target_service_accounts: null
+ module.folder1.google_folder.folder[0]:
+ display_name: policy-container
+ parent: organizations/1122334455
+ module.folder2.google_compute_firewall_policy_association.association["iap-policy"]: {}
+ module.folder2.google_folder.folder[0]:
+ display_name: hf2
+ parent: organizations/1122334455
counts:
- google_compute_firewall_policy: 2
- google_compute_firewall_policy_rule: 3
+ google_compute_firewall_policy: 1
+ google_compute_firewall_policy_association: 2
+ google_compute_firewall_policy_rule: 2
+ google_folder: 2
diff --git a/tests/modules/folder/examples/iam.yaml b/tests/modules/folder/examples/iam.yaml
new file mode 100644
index 000000000..6f0fe2e51
--- /dev/null
+++ b/tests/modules/folder/examples/iam.yaml
@@ -0,0 +1,59 @@
+# 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.
+
+values:
+ module.folder.google_folder.folder[0]:
+ display_name: Folder name
+ parent: organizations/1234567890
+ module.folder.google_folder_iam_binding.authoritative["roles/owner"]:
+ condition: []
+ members:
+ - group:cloud-owners@example.org
+ - user:one@example.org
+ role: roles/owner
+ module.folder.google_folder_iam_binding.authoritative["roles/resourcemanager.folderAdmin"]:
+ condition: []
+ members:
+ - group:cloud-owners@example.org
+ role: roles/resourcemanager.folderAdmin
+ module.folder.google_folder_iam_binding.authoritative["roles/resourcemanager.projectCreator"]:
+ condition: []
+ members:
+ - group:cloud-owners@example.org
+ role: roles/resourcemanager.projectCreator
+ module.folder.google_folder_iam_member.additive["roles/compute.admin-user:a1@example.org"]:
+ condition: []
+ member: user:a1@example.org
+ role: roles/compute.admin
+ module.folder.google_folder_iam_member.additive["roles/compute.admin-user:a2@example.org"]:
+ condition: []
+ member: user:a2@example.org
+ role: roles/compute.admin
+ module.folder.google_folder_iam_member.additive["roles/compute.viewer-user:a2@example.org"]:
+ condition: []
+ member: user:a2@example.org
+ role: roles/compute.viewer
+ module.folder.google_folder_iam_member.additive["roles/storage.admin-user:am1@example.org"]:
+ condition: []
+ member: user:am1@example.org
+ role: roles/storage.admin
+ module.folder.google_folder_iam_member.additive["roles/storage.objectViewer-user:am2@example.org"]:
+ condition: []
+ member: user:am2@example.org
+ role: roles/storage.objectViewer
+
+counts:
+ google_folder: 1
+ google_folder_iam_binding: 3
+ google_folder_iam_member: 5
diff --git a/tests/modules/folder/examples/logging.yaml b/tests/modules/folder/examples/logging.yaml
new file mode 100644
index 000000000..79b0e0078
--- /dev/null
+++ b/tests/modules/folder/examples/logging.yaml
@@ -0,0 +1,75 @@
+# 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.
+
+values:
+ module.folder-sink.google_bigquery_dataset_iam_member.bq-sinks-binding["info"]:
+ role: roles/bigquery.dataEditor
+ module.folder-sink.google_folder.folder[0]:
+ display_name: my-folder
+ parent: folders/657104291943
+ module.folder-sink.google_logging_folder_exclusion.logging-exclusion["no-gce-instances"]:
+ description: no-gce-instances (Terraform-managed).
+ filter: resource.type=gce_instance
+ name: no-gce-instances
+ module.folder-sink.google_logging_folder_sink.sink["debug"]:
+ disabled: false
+ exclusions:
+ - description: null
+ disabled: false
+ filter: logName:compute
+ name: no-compute
+ filter: severity=DEBUG
+ include_children: true
+ name: debug
+ module.folder-sink.google_logging_folder_sink.sink["info"]:
+ disabled: false
+ exclusions: []
+ filter: severity=INFO
+ include_children: true
+ name: info
+ module.folder-sink.google_logging_folder_sink.sink["notice"]:
+ disabled: false
+ exclusions: []
+ filter: severity=NOTICE
+ include_children: true
+ name: notice
+ module.folder-sink.google_logging_folder_sink.sink["warnings"]:
+ description: warnings (Terraform-managed).
+ destination: storage.googleapis.com/gcs_sink
+ disabled: false
+ exclusions: []
+ filter: severity=WARNING
+ include_children: true
+ name: warnings
+ module.folder-sink.google_project_iam_member.bucket-sinks-binding["debug"]:
+ condition:
+ - title: debug bucket writer
+ role: roles/logging.bucketWriter
+ module.folder-sink.google_pubsub_topic_iam_member.pubsub-sinks-binding["notice"]:
+ condition: []
+ role: roles/pubsub.publisher
+ module.folder-sink.google_storage_bucket_iam_member.gcs-sinks-binding["warnings"]:
+ bucket: gcs_sink
+ condition: []
+ role: roles/storage.objectCreator
+
+counts:
+ google_bigquery_dataset_iam_member: 1
+ google_folder: 1
+ google_logging_folder_exclusion: 1
+ google_logging_folder_sink: 4
+ google_logging_project_bucket_config: 1
+ google_project_iam_member: 1
+ google_pubsub_topic_iam_member: 1
+ google_storage_bucket_iam_member: 1
diff --git a/tests/modules/folder/examples/org-policies.yaml b/tests/modules/folder/examples/org-policies.yaml
new file mode 100644
index 000000000..f8bf41879
--- /dev/null
+++ b/tests/modules/folder/examples/org-policies.yaml
@@ -0,0 +1,108 @@
+# 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.
+
+values:
+ module.folder.google_folder.folder[0]:
+ display_name: Folder name
+ parent: organizations/1234567890
+ module.folder.google_org_policy_policy.default["compute.disableGuestAttributesAccess"]:
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ module.folder.google_org_policy_policy.default["constraints/compute.skipDefaultNetworkCreation"]:
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ module.folder.google_org_policy_policy.default["constraints/compute.trustedImageProjects"]:
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: null
+ values:
+ - allowed_values:
+ - projects/my-project
+ denied_values: null
+ module.folder.google_org_policy_policy.default["constraints/compute.vmExternalIpAccess"]:
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: 'TRUE'
+ enforce: null
+ values: []
+ module.folder.google_org_policy_policy.default["constraints/iam.allowedPolicyMemberDomains"]:
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: null
+ values:
+ - allowed_values:
+ - C0xxxxxxx
+ - C0yyyyyyy
+ denied_values: null
+ module.folder.google_org_policy_policy.default["iam.disableServiceAccountKeyCreation"]:
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ module.folder.google_org_policy_policy.default["iam.disableServiceAccountKeyUpload"]:
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition:
+ - description: test condition
+ expression: resource.matchTagId("tagKeys/1234", "tagValues/1234")
+ location: somewhere
+ title: condition
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'FALSE'
+ values: []
+
+counts:
+ google_folder: 1
+ google_org_policy_policy: 7
diff --git a/tests/modules/folder/examples/tags.yaml b/tests/modules/folder/examples/tags.yaml
new file mode 100644
index 000000000..047fea06d
--- /dev/null
+++ b/tests/modules/folder/examples/tags.yaml
@@ -0,0 +1,41 @@
+# 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.
+
+
+tests/examples/test_plan.py::test_example[modules/folder:Tags] values:
+ module.folder.google_folder.folder[0]:
+ display_name: Test
+ parent: organizations/1122334455
+ module.folder.google_tags_tag_binding.binding["env-prod"]: {}
+ module.folder.google_tags_tag_binding.binding["foo"]:
+ tag_value: tagValues/12345678
+ module.org.google_tags_tag_key.default["environment"]:
+ description: Environment specification.
+ parent: organizations/1122334455
+ purpose: null
+ purpose_data: null
+ short_name: environment
+ timeouts: null
+ module.org.google_tags_tag_value.default["environment/dev"]:
+ description: Managed by the Terraform organization module.
+ short_name: dev
+ module.org.google_tags_tag_value.default["environment/prod"]:
+ description: Managed by the Terraform organization module.
+ short_name: prod
+
+counts:
+ google_folder: 1
+ google_tags_tag_binding: 2
+ google_tags_tag_key: 1
+ google_tags_tag_value: 2
diff --git a/tests/modules/folder/fixture/main.tf b/tests/modules/folder/fixture/main.tf
deleted file mode 100644
index a347f61bb..000000000
--- a/tests/modules/folder/fixture/main.tf
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * 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 "test" {
- source = "../../../../modules/folder"
- parent = "organizations/12345678"
- name = "folder-a"
- group_iam = var.group_iam
- iam = var.iam
- iam_additive = var.iam_additive
- iam_additive_members = var.iam_additive_members
- firewall_policies = var.firewall_policies
- firewall_policy_association = var.firewall_policy_association
- logging_sinks = var.logging_sinks
- logging_exclusions = var.logging_exclusions
- org_policies = var.org_policies
- org_policies_data_path = var.org_policies_data_path
-}
diff --git a/tests/modules/folder/fixture/test.logging-sinks.tfvars b/tests/modules/folder/fixture/test.logging-sinks.tfvars
deleted file mode 100644
index 95a272e1f..000000000
--- a/tests/modules/folder/fixture/test.logging-sinks.tfvars
+++ /dev/null
@@ -1,29 +0,0 @@
-logging_sinks = {
- warning = {
- destination = "mybucket"
- type = "storage"
- filter = "severity=WARNING"
- }
- info = {
- destination = "projects/myproject/datasets/mydataset"
- type = "bigquery"
- filter = "severity=INFO"
- disabled = true
- }
- notice = {
- destination = "projects/myproject/topics/mytopic"
- type = "pubsub"
- filter = "severity=NOTICE"
- include_children = false
- }
- debug = {
- destination = "projects/myproject/locations/global/buckets/mybucket"
- type = "logging"
- filter = "severity=DEBUG"
- include_children = false
- exclusions = {
- no-compute = "logName:compute"
- no-container = "logName:container"
- }
- }
-}
diff --git a/tests/modules/folder/fixture/variables.tf b/tests/modules/folder/fixture/variables.tf
deleted file mode 100644
index e2d7a293b..000000000
--- a/tests/modules/folder/fixture/variables.tf
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * 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 "group_iam" {
- type = any
- default = {}
-}
-
-variable "iam" {
- type = any
- default = {}
-}
-
-variable "iam_additive" {
- type = any
- default = {}
-}
-
-variable "iam_additive_members" {
- type = any
- default = {}
-}
-
-variable "firewall_policies" {
- type = any
- default = {}
-}
-
-variable "firewall_policy_association" {
- type = any
- default = {}
-}
-
-variable "logging_sinks" {
- type = any
- default = {}
-}
-
-variable "logging_exclusions" {
- type = any
- default = {}
-}
-
-variable "org_policies" {
- type = any
- default = {}
-}
-
-variable "org_policies_data_path" {
- type = any
- default = null
-}
diff --git a/tests/modules/folder/fixture/test.orgpolicies-boolean.tfvars b/tests/modules/folder/org_policies_boolean.tfvars
similarity index 100%
rename from tests/modules/folder/fixture/test.orgpolicies-boolean.tfvars
rename to tests/modules/folder/org_policies_boolean.tfvars
diff --git a/tests/modules/folder/fixture/test.orgpolicies-list.tfvars b/tests/modules/folder/org_policies_list.tfvars
similarity index 100%
rename from tests/modules/folder/fixture/test.orgpolicies-list.tfvars
rename to tests/modules/folder/org_policies_list.tfvars
diff --git a/tests/modules/folder/test_plan.py b/tests/modules/folder/test_plan.py
deleted file mode 100644
index 0ce1ae4a8..000000000
--- a/tests/modules/folder/test_plan.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# 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_folder(plan_runner):
- "Test folder resources."
- _, resources = plan_runner()
- assert len(resources) == 1
- resource = resources[0]
- assert resource['type'] == 'google_folder'
- assert resource['values']['display_name'] == 'folder-a'
- assert resource['values']['parent'] == 'organizations/12345678'
-
-
-def test_iam(plan_runner):
- "Test IAM."
- group_iam = (
- '{'
- '"owners@example.org" = ["roles/owner", "roles/resourcemanager.folderAdmin"],'
- '"viewers@example.org" = ["roles/viewer"]'
- '}')
- iam = ('{'
- '"roles/owner" = ["user:one@example.org", "user:two@example.org"],'
- '"roles/browser" = ["domain:example.org"]'
- '}')
- _, resources = plan_runner(group_iam=group_iam, iam=iam)
- roles = sorted([(r['values']['role'], sorted(r['values']['members']))
- for r in resources
- if r['type'] == 'google_folder_iam_binding'])
- assert roles == [
- ('roles/browser', ['domain:example.org']),
- ('roles/owner', [
- 'group:owners@example.org', 'user:one@example.org',
- 'user:two@example.org'
- ]),
- ('roles/resourcemanager.folderAdmin', ['group:owners@example.org']),
- ('roles/viewer', ['group:viewers@example.org']),
- ]
-
-
-def test_iam_multiple_roles(plan_runner):
- "Test folder resources with multiple iam roles."
- iam = ('{ '
- '"roles/owner" = ["user:a@b.com"], '
- '"roles/viewer" = ["user:c@d.com"] '
- '} ')
- _, resources = plan_runner(iam=iam)
- assert len(resources) == 3
-
-
-def test_iam_additive_members(plan_runner):
- "Test IAM additive members."
- iam = ('{"user:one@example.org" = ["roles/owner"],'
- '"user:two@example.org" = ["roles/owner", "roles/editor"]}')
- _, resources = plan_runner(iam_additive_members=iam)
- roles = set((r['values']['role'], r['values']['member'])
- for r in resources
- if r['type'] == 'google_folder_iam_member')
- assert roles == set([('roles/owner', 'user:one@example.org'),
- ('roles/owner', 'user:two@example.org'),
- ('roles/editor', 'user:two@example.org')])
diff --git a/tests/modules/folder/test_plan_firewall_policy.py b/tests/modules/folder/test_plan_firewall_policy.py
deleted file mode 100644
index 4364fbdf1..000000000
--- a/tests/modules/folder/test_plan_firewall_policy.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# 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_firweall_policy(plan_runner):
- "Test boolean folder policy."
- policy = """
- {
- policy1 = {
- allow-ingress = {
- description = ""
- direction = "INGRESS"
- action = "allow"
- priority = 100
- ranges = ["10.0.0.0/8"]
- ports = {
- tcp = ["22"]
- }
- target_service_accounts = null
- target_resources = null
- logging = false
- }
- deny-egress = {
- description = ""
- direction = "EGRESS"
- action = "deny"
- priority = 200
- ranges = ["192.168.0.0/24"]
- ports = {
- tcp = ["443"]
- }
- target_service_accounts = null
- target_resources = null
- logging = false
- }
- }
- }
- """
- association = '{policy1="policy1"}'
- _, resources = plan_runner(firewall_policies=policy,
- firewall_policy_association=association)
- assert len(resources) == 5
-
- policies = [r for r in resources
- if r['type'] == 'google_compute_firewall_policy']
- assert len(policies) == 1
-
- rules = [r for r in resources
- if r['type'] == 'google_compute_firewall_policy_rule']
- assert len(rules) == 2
-
- rule_values = []
- for rule in rules:
- name = rule['name']
- index = rule['index']
- action = rule['values']['action']
- direction = rule['values']['direction']
- priority = rule['values']['priority']
- match = rule['values']['match']
- rule_values.append((name, index, action, direction, priority, match))
-
- assert sorted(rule_values) == sorted([
- ('rule', 'policy1-allow-ingress', 'allow', 'INGRESS', 100, [
- {
- 'dest_ip_ranges': None,
- 'layer4_configs': [{'ip_protocol': 'tcp', 'ports': ['22']}],
- 'src_ip_ranges': ['10.0.0.0/8']
- }]),
- ('rule', 'policy1-deny-egress', 'deny', 'EGRESS', 200, [
- {
- 'dest_ip_ranges': ['192.168.0.0/24'],
- 'layer4_configs': [{'ip_protocol': 'tcp', 'ports': ['443']}],
- 'src_ip_ranges': None
- }])
- ])
diff --git a/tests/modules/folder/test_plan_logging.py b/tests/modules/folder/test_plan_logging.py
deleted file mode 100644
index 6b305d0b1..000000000
--- a/tests/modules/folder/test_plan_logging.py
+++ /dev/null
@@ -1,119 +0,0 @@
-# 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.
-
-from collections import Counter
-
-
-def test_sinks(plan_runner):
- "Test folder-level sinks."
- tfvars = 'test.logging-sinks.tfvars'
- _, resources = plan_runner(tf_var_file=tfvars)
- assert len(resources) == 9
-
- resource_types = Counter([r["type"] for r in resources])
- assert resource_types == {
- "google_logging_folder_sink": 4,
- "google_folder": 1,
- "google_bigquery_dataset_iam_member": 1,
- "google_project_iam_member": 1,
- "google_pubsub_topic_iam_member": 1,
- "google_storage_bucket_iam_member": 1,
- }
-
- sinks = [r for r in resources if r["type"] == "google_logging_folder_sink"]
- assert sorted([r["index"] for r in sinks]) == [
- "debug",
- "info",
- "notice",
- "warning",
- ]
- values = [(
- r["index"],
- r["values"]["filter"],
- r["values"]["destination"],
- r["values"]["description"],
- r["values"]["include_children"],
- r["values"]["disabled"],
- ) for r in sinks]
- assert sorted(values) == [
- ("debug", "severity=DEBUG",
- "logging.googleapis.com/projects/myproject/locations/global/buckets/mybucket",
- "debug (Terraform-managed).", False, False),
- ("info", "severity=INFO",
- "bigquery.googleapis.com/projects/myproject/datasets/mydataset",
- "info (Terraform-managed).", True, True),
- ("notice", "severity=NOTICE",
- "pubsub.googleapis.com/projects/myproject/topics/mytopic",
- "notice (Terraform-managed).", False, False),
- ("warning", "severity=WARNING", "storage.googleapis.com/mybucket",
- "warning (Terraform-managed).", True, False),
- ]
-
- bindings = [r for r in resources if "member" in r["type"]]
- values = [(r["index"], r["type"], r["values"]["role"],
- r["values"]["condition"]) for r in bindings]
- assert sorted(values) == [
- ("debug", "google_project_iam_member", "roles/logging.bucketWriter", [{
- 'expression':
- "resource.name.endsWith('projects/myproject/locations/global/buckets/mybucket')",
- 'title':
- 'debug bucket writer'
- }]),
- ("info", "google_bigquery_dataset_iam_member",
- "roles/bigquery.dataEditor", []),
- ("notice", "google_pubsub_topic_iam_member", "roles/pubsub.publisher",
- []),
- ("warning", "google_storage_bucket_iam_member",
- "roles/storage.objectCreator", []),
- ]
-
- exclusions = [(r["index"], r["values"]["exclusions"]) for r in sinks]
- assert sorted(exclusions) == [
- ("debug", [{
- "description": None,
- "disabled": False,
- "filter": "logName:compute",
- "name": "no-compute"
- }, {
- "description": None,
- "disabled": False,
- "filter": "logName:container",
- "name": "no-container"
- }]),
- ("info", []),
- ("notice", []),
- ("warning", []),
- ]
-
-
-def test_exclusions(plan_runner):
- "Test folder-level logging exclusions."
- logging_exclusions = ("{"
- 'exclusion1 = "resource.type=gce_instance", '
- 'exclusion2 = "severity=NOTICE", '
- "}")
- _, resources = plan_runner(logging_exclusions=logging_exclusions)
- assert len(resources) == 3
- exclusions = [
- r for r in resources if r["type"] == "google_logging_folder_exclusion"
- ]
- assert sorted([r["index"] for r in exclusions]) == [
- "exclusion1",
- "exclusion2",
- ]
- values = [(r["index"], r["values"]["filter"]) for r in exclusions]
- assert sorted(values) == [
- ("exclusion1", "resource.type=gce_instance"),
- ("exclusion2", "severity=NOTICE"),
- ]
diff --git a/tests/modules/folder/test_plan_org_policies.py b/tests/modules/folder/test_plan_org_policies.py
index 8463761e8..161845376 100644
--- a/tests/modules/folder/test_plan_org_policies.py
+++ b/tests/modules/folder/test_plan_org_policies.py
@@ -12,33 +12,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from .validate_policies import validate_policy_boolean, validate_policy_list
+import pytest
+
+_params = ['boolean', 'list']
-def test_policy_boolean(plan_runner):
- "Test boolean org policy."
- tfvars = 'test.orgpolicies-boolean.tfvars'
- _, resources = plan_runner(tf_var_file=tfvars)
- validate_policy_boolean(resources)
-
-
-def test_policy_list(plan_runner):
- "Test list org policy."
- tfvars = 'test.orgpolicies-list.tfvars'
- _, resources = plan_runner(tf_var_file=tfvars)
- validate_policy_list(resources)
-
-
-def test_factory_policy_boolean(plan_runner, tfvars_to_yaml, tmp_path):
+@pytest.mark.parametrize('policy_type', _params)
+def test_policy_factory(plan_summary, tfvars_to_yaml, tmp_path, policy_type):
dest = tmp_path / 'policies.yaml'
- tfvars_to_yaml('fixture/test.orgpolicies-boolean.tfvars', dest,
- 'org_policies')
- _, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
- validate_policy_boolean(resources)
-
-
-def test_factory_policy_list(plan_runner, tfvars_to_yaml, tmp_path):
- dest = tmp_path / 'policies.yaml'
- tfvars_to_yaml('fixture/test.orgpolicies-list.tfvars', dest, 'org_policies')
- _, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
- validate_policy_list(resources)
+ tfvars_to_yaml(f'org_policies_{policy_type}.tfvars', dest, 'org_policies')
+ tfvars_plan = plan_summary(
+ 'modules/folder',
+ tf_var_files=['common.tfvars', f'org_policies_{policy_type}.tfvars'])
+ yaml_plan = plan_summary('modules/folder', tf_var_files=['common.tfvars'],
+ org_policies_data_path=f'{tmp_path}')
+ assert tfvars_plan.values == yaml_plan.values
diff --git a/tests/modules/folder/validate_policies.py b/tests/modules/folder/validate_policies.py
deleted file mode 100644
index 385898b17..000000000
--- a/tests/modules/folder/validate_policies.py
+++ /dev/null
@@ -1,158 +0,0 @@
-# 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 validate_policy_boolean(resources):
- assert len(resources) == 3
- policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
- assert len(policies) == 2
-
- p1 = [
- r['values']['spec'][0]
- for r in policies
- if r['index'] == 'iam.disableServiceAccountKeyCreation'
- ][0]
-
- assert p1['inherit_from_parent'] is None
- assert p1['reset'] is None
- assert p1['rules'] == [{
- 'allow_all': None,
- 'condition': [],
- 'deny_all': None,
- 'enforce': 'TRUE',
- 'values': []
- }]
-
- p2 = [
- r['values']['spec'][0]
- for r in policies
- if r['index'] == 'iam.disableServiceAccountKeyUpload'
- ][0]
-
- assert p2['inherit_from_parent'] is None
- assert p2['reset'] is None
- assert len(p2['rules']) == 2
- assert p2['rules'][0] == {
- 'allow_all': None,
- 'condition': [],
- 'deny_all': None,
- 'enforce': 'FALSE',
- 'values': []
- }
- assert p2['rules'][1] == {
- 'allow_all': None,
- 'condition': [{
- 'description': 'test condition',
- 'expression': 'resource.matchTagId(aa, bb)',
- 'location': 'xxx',
- 'title': 'condition'
- }],
- 'deny_all': None,
- 'enforce': 'TRUE',
- 'values': []
- }
-
-
-def validate_policy_list(resources):
- assert len(resources) == 4
-
- policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
- assert len(policies) == 3
-
- p1 = [
- r['values']['spec'][0]
- for r in policies
- if r['index'] == 'compute.vmExternalIpAccess'
- ][0]
- assert p1['inherit_from_parent'] is None
- assert p1['reset'] is None
- assert p1['rules'] == [{
- 'allow_all': None,
- 'condition': [],
- 'deny_all': 'TRUE',
- 'enforce': None,
- 'values': []
- }]
-
- p2 = [
- r['values']['spec'][0]
- for r in policies
- if r['index'] == 'iam.allowedPolicyMemberDomains'
- ][0]
- assert p2['inherit_from_parent'] is None
- assert p2['reset'] is None
- assert p2['rules'] == [{
- 'allow_all':
- None,
- 'condition': [],
- 'deny_all':
- None,
- 'enforce':
- None,
- 'values': [{
- 'allowed_values': [
- 'C0xxxxxxx',
- 'C0yyyyyyy',
- ],
- 'denied_values': None
- }]
- }]
-
- p3 = [
- r['values']['spec'][0]
- for r in policies
- if r['index'] == 'compute.restrictLoadBalancerCreationForTypes'
- ][0]
- assert p3['inherit_from_parent'] is None
- assert p3['reset'] is None
- assert len(p3['rules']) == 3
- assert p3['rules'][0] == {
- 'allow_all': None,
- 'condition': [],
- 'deny_all': None,
- 'enforce': None,
- 'values': [{
- 'allowed_values': None,
- 'denied_values': ['in:EXTERNAL']
- }]
- }
-
- assert p3['rules'][1] == {
- 'allow_all': None,
- 'condition': [{
- 'description': 'test condition',
- 'expression': 'resource.matchTagId(aa, bb)',
- 'location': 'xxx',
- 'title': 'condition'
- }],
- 'deny_all': None,
- 'enforce': None,
- 'values': [{
- 'allowed_values': ['EXTERNAL_1'],
- 'denied_values': None
- }]
- }
-
- assert p3['rules'][2] == {
- 'allow_all': 'TRUE',
- 'condition': [{
- 'description': 'test condition2',
- 'expression': 'resource.matchTagId(cc, dd)',
- 'location': 'xxx',
- 'title': 'condition2'
- }],
- 'deny_all': None,
- 'enforce': None,
- 'values': []
- }
diff --git a/tests/modules/gcs/examples/cmek.yaml b/tests/modules/gcs/examples/cmek.yaml
new file mode 100644
index 000000000..ee92a5d22
--- /dev/null
+++ b/tests/modules/gcs/examples/cmek.yaml
@@ -0,0 +1,23 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.bucket.google_storage_bucket.bucket:
+ encryption:
+ - default_kms_key_name: my-encryption-key
+ name: my-bucket
+ project: myproject
+
+counts:
+ google_storage_bucket: 1
diff --git a/tests/modules/gcs/examples/lifecycle.yaml b/tests/modules/gcs/examples/lifecycle.yaml
new file mode 100644
index 000000000..69eeea41f
--- /dev/null
+++ b/tests/modules/gcs/examples/lifecycle.yaml
@@ -0,0 +1,38 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.bucket.google_storage_bucket.bucket:
+ lifecycle_rule:
+ - action:
+ - storage_class: STANDARD
+ type: SetStorageClass
+ condition:
+ - age: 30
+ created_before: ''
+ custom_time_before: ''
+ days_since_custom_time: null
+ days_since_noncurrent_time: null
+ matches_prefix: []
+ matches_storage_class: []
+ matches_suffix: []
+ noncurrent_time_before: ''
+ num_newer_versions: null
+ name: my-bucket
+ project: myproject
+
+counts:
+ google_storage_bucket: 1
+
+outputs: {}
diff --git a/tests/modules/gcs/examples/notification.yaml b/tests/modules/gcs/examples/notification.yaml
new file mode 100644
index 000000000..9536e89b4
--- /dev/null
+++ b/tests/modules/gcs/examples/notification.yaml
@@ -0,0 +1,31 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.bucket-gcs-notification.google_pubsub_topic.topic[0]: {}
+ module.bucket-gcs-notification.google_pubsub_topic_iam_binding.binding[0]: {}
+ module.bucket-gcs-notification.google_storage_bucket.bucket:
+ name: my-bucket
+ project: myproject
+ module.bucket-gcs-notification.google_storage_notification.notification[0]:
+ bucket: my-bucket
+ event_types:
+ - OBJECT_FINALIZE
+ payload_format: JSON_API_V1
+
+counts:
+ google_pubsub_topic: 1
+ google_pubsub_topic_iam_binding: 1
+ google_storage_bucket: 1
+ google_storage_notification: 1
diff --git a/tests/modules/gcs/examples/retention-logging.yaml b/tests/modules/gcs/examples/retention-logging.yaml
new file mode 100644
index 000000000..962414207
--- /dev/null
+++ b/tests/modules/gcs/examples/retention-logging.yaml
@@ -0,0 +1,26 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.bucket.google_storage_bucket.bucket:
+ logging:
+ - log_bucket: log-bucket
+ name: my-bucket
+ project: myproject
+ retention_policy:
+ - is_locked: true
+ retention_period: 100
+
+counts:
+ google_storage_bucket: 1
diff --git a/tests/modules/gcs/examples/simple.yaml b/tests/modules/gcs/examples/simple.yaml
new file mode 100644
index 000000000..bc2630b87
--- /dev/null
+++ b/tests/modules/gcs/examples/simple.yaml
@@ -0,0 +1,46 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.bucket.google_storage_bucket.bucket:
+ autoclass: []
+ cors: []
+ custom_placement_config: []
+ default_event_based_hold: null
+ encryption: []
+ force_destroy: false
+ labels:
+ cost-center: devops
+ lifecycle_rule: []
+ location: EU
+ logging: []
+ name: test-my-bucket
+ project: myproject
+ requester_pays: null
+ retention_policy: []
+ storage_class: MULTI_REGIONAL
+ timeouts: null
+ uniform_bucket_level_access: true
+ versioning:
+ - enabled: true
+ module.bucket.google_storage_bucket_iam_binding.bindings["roles/storage.admin"]:
+ bucket: test-my-bucket
+ condition: []
+ members:
+ - group:storage@example.com
+ role: roles/storage.admin
+
+counts:
+ google_storage_bucket: 1
+ google_storage_bucket_iam_binding: 1
diff --git a/tests/modules/gcs/fixture/main.tf b/tests/modules/gcs/fixture/main.tf
deleted file mode 100644
index ea2e994f6..000000000
--- a/tests/modules/gcs/fixture/main.tf
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * 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 "test" {
- source = "../../../../modules/gcs"
- project_id = "my-project"
- uniform_bucket_level_access = var.uniform_bucket_level_access
- force_destroy = var.force_destroy
- iam = var.iam
- labels = var.labels
- logging_config = var.logging_config
- name = "bucket-a"
- prefix = var.prefix
- retention_policy = var.retention_policy
- versioning = var.versioning
-}
diff --git a/tests/modules/gcs/fixture/variables.tf b/tests/modules/gcs/fixture/variables.tf
deleted file mode 100644
index 455d9a4bb..000000000
--- a/tests/modules/gcs/fixture/variables.tf
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * 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 "uniform_bucket_level_access" {
- type = bool
- default = false
-}
-
-variable "force_destroy" {
- type = bool
- default = true
-}
-
-variable "iam" {
- type = map(list(string))
- default = {}
-}
-
-variable "labels" {
- type = map(string)
- default = { environment = "test" }
-}
-
-variable "logging_config" {
- type = object({
- log_bucket = string
- log_object_prefix = string
- })
- default = {
- log_bucket = "foo"
- log_object_prefix = null
- }
-}
-
-variable "prefix" {
- type = string
- default = null
-}
-
-variable "project_id" {
- type = string
- default = "my-project"
-}
-
-variable "retention_policy" {
- type = object({
- retention_period = number
- is_locked = bool
- })
- default = {
- retention_period = 5
- is_locked = false
- }
-}
-
-variable "storage_class" {
- type = string
- default = "MULTI_REGIONAL"
-}
-
-variable "versioning" {
- type = bool
- default = true
-}
diff --git a/tests/modules/gcs/test_plan.py b/tests/modules/gcs/test_plan.py
deleted file mode 100644
index 22775a589..000000000
--- a/tests/modules/gcs/test_plan.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# 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_buckets(plan_runner):
- "Test bucket resources."
- _, resources = plan_runner()
- assert len(resources) == 1
- r = resources[0]
- assert r['type'] == 'google_storage_bucket'
- assert r['values']['name'] == 'bucket-a'
- assert r['values']['project'] == 'my-project'
-
-
-def test_prefix(plan_runner):
- "Test bucket name when prefix is set."
- _, resources = plan_runner(prefix='foo')
- assert resources[0]['values']['name'] == 'foo-bucket-a'
-
-
-def test_config_values(plan_runner):
- "Test that variables set the correct attributes on buckets."
- variables = dict(
- uniform_bucket_level_access='true',
- force_destroy='true',
- versioning='true'
- )
- _, resources = plan_runner(**variables)
- assert len(resources) == 1
- r = resources[0]
- assert r['values']['uniform_bucket_level_access'] is True
- assert r['values']['force_destroy'] is True
- assert r['values']['versioning'] == [{'enabled': True}]
- assert r['values']['logging'] == [{'log_bucket': 'foo'}]
- assert r['values']['retention_policy'] == [
- {'is_locked': False, 'retention_period': 5}
- ]
-
-
-def test_iam(plan_runner):
- "Test bucket resources with iam roles and members."
- iam = '{ "roles/storage.admin" = ["user:a@b.com"] }'
- _, resources = plan_runner(iam=iam)
- assert len(resources) == 2
diff --git a/tests/modules/gke_cluster/examples/autopilot.yaml b/tests/modules/gke_cluster/examples/autopilot.yaml
new file mode 100644
index 000000000..0a5380dbb
--- /dev/null
+++ b/tests/modules/gke_cluster/examples/autopilot.yaml
@@ -0,0 +1,32 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.cluster-autopilot.google_container_cluster.cluster:
+ enable_autopilot: true
+ ip_allocation_policy:
+ - cluster_secondary_range_name: pods
+ services_secondary_range_name: services
+ location: europe-west1-b
+ master_authorized_networks_config:
+ - cidr_blocks:
+ - cidr_block: 10.0.0.0/8
+ display_name: internal-vms
+ name: cluster-autopilot
+ network: projects/xxx/global/networks/aaa
+ project: myproject
+ subnetwork: subnet_self_link
+
+counts:
+ google_container_cluster: 1
diff --git a/tests/modules/gke_cluster/examples/basic.yaml b/tests/modules/gke_cluster/examples/basic.yaml
new file mode 100644
index 000000000..fe6648c8d
--- /dev/null
+++ b/tests/modules/gke_cluster/examples/basic.yaml
@@ -0,0 +1,42 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.cluster-1.google_container_cluster.cluster:
+ default_max_pods_per_node: 32
+ ip_allocation_policy:
+ - cluster_secondary_range_name: pods
+ services_secondary_range_name: services
+ location: europe-west1-b
+ master_authorized_networks_config:
+ - cidr_blocks:
+ - cidr_block: 10.0.0.0/8
+ display_name: internal-vms
+ name: cluster-1
+ network: projects/xxx/global/networks/aaa
+ private_cluster_config:
+ - enable_private_endpoint: true
+ enable_private_nodes: true
+ master_global_access_config:
+ - enabled: false
+ master_ipv4_cidr_block: 192.168.0.0/28
+ private_endpoint_subnetwork: null
+ project: myproject
+ remove_default_node_pool: true
+ resource_labels:
+ environment: dev
+ subnetwork: subnet_self_link
+
+counts:
+ google_container_cluster: 1
diff --git a/tests/modules/gke_cluster/examples/dataplane-v2.yaml b/tests/modules/gke_cluster/examples/dataplane-v2.yaml
new file mode 100644
index 000000000..ef7ca642f
--- /dev/null
+++ b/tests/modules/gke_cluster/examples/dataplane-v2.yaml
@@ -0,0 +1,45 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.cluster-1.google_container_cluster.cluster:
+ datapath_provider: ADVANCED_DATAPATH
+ ip_allocation_policy:
+ - cluster_secondary_range_name: pods
+ services_secondary_range_name: services
+ location: europe-west1-b
+ master_authorized_networks_config:
+ - cidr_blocks:
+ - cidr_block: 10.0.0.0/8
+ display_name: internal-vms
+ min_master_version: null
+ name: cluster-dataplane-v2
+ network: projects/xxx/global/networks/aaa
+ private_cluster_config:
+ - enable_private_endpoint: true
+ enable_private_nodes: true
+ master_global_access_config:
+ - enabled: false
+ master_ipv4_cidr_block: 192.168.0.0/28
+ private_endpoint_subnetwork: null
+ project: myproject
+ remove_default_node_pool: true
+ resource_labels:
+ environment: dev
+ subnetwork: subnet_self_link
+ workload_identity_config:
+ - workload_pool: myproject.svc.id.goog
+
+counts:
+ google_container_cluster: 1
diff --git a/tests/modules/gke_cluster/examples/dns.yaml b/tests/modules/gke_cluster/examples/dns.yaml
new file mode 100644
index 000000000..53792e051
--- /dev/null
+++ b/tests/modules/gke_cluster/examples/dns.yaml
@@ -0,0 +1,28 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.cluster-1.google_container_cluster.cluster:
+ dns_config:
+ - cluster_dns: CLOUD_DNS
+ cluster_dns_domain: gke.local
+ cluster_dns_scope: CLUSTER_SCOPE
+ ip_allocation_policy:
+ - cluster_secondary_range_name: pods
+ services_secondary_range_name: services
+ location: europe-west1-b
+ name: cluster-1
+
+counts:
+ google_container_cluster: 1
diff --git a/tests/modules/gke_cluster/test_plan.py b/tests/modules/gke_cluster/test_plan.py
deleted file mode 100644
index acd97bede..000000000
--- a/tests/modules/gke_cluster/test_plan.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# 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_standard(plan_runner):
- "Test resources created with variable defaults."
- _, resources = plan_runner()
- assert len(resources) == 1
-
- cluster_config = resources[0]['values']
- assert cluster_config['name'] == "cluster-1"
- assert cluster_config['network'] == "mynetwork"
- assert cluster_config['subnetwork'] == "mysubnet"
- assert cluster_config['enable_autopilot'] is None
- # assert 'service_account' not in node_config
-
-
-def test_autopilot(plan_runner):
- "Test resources created with variable defaults."
- _, resources = plan_runner(enable_features='{ autopilot=true }')
- assert len(resources) == 1
- cluster_config = resources[0]['values']
- assert cluster_config['name'] == "cluster-1"
- assert cluster_config['network'] == "mynetwork"
- assert cluster_config['subnetwork'] == "mysubnet"
- assert cluster_config['enable_autopilot'] == True
- # assert 'service_account' not in node_config
diff --git a/tests/modules/gke_hub/fixture/variables.tf b/tests/modules/gke_hub/fixture/variables.tf
index 5c5c106f2..1d76d4f97 100644
--- a/tests/modules/gke_hub/fixture/variables.tf
+++ b/tests/modules/gke_hub/fixture/variables.tf
@@ -31,7 +31,7 @@ variable "features" {
configmanagement = true
identityservice = false
multiclusteringress = null
- servicemesh = false
+ servicemesh = true
multiclusterservicediscovery = false
}
}
diff --git a/tests/modules/gke_hub/test_plan.py b/tests/modules/gke_hub/test_plan.py
index 355218134..8a71d12b5 100644
--- a/tests/modules/gke_hub/test_plan.py
+++ b/tests/modules/gke_hub/test_plan.py
@@ -23,11 +23,14 @@ def resources(plan_runner):
def test_resource_count(resources):
"Test number of resources created."
- assert len(resources) == 5
+ assert len(resources) == 8
assert sorted(r['address'] for r in resources) == [
'module.hub.google_gke_hub_feature.default["configmanagement"]',
+ 'module.hub.google_gke_hub_feature.default["servicemesh"]',
'module.hub.google_gke_hub_feature_membership.default["cluster-1"]',
'module.hub.google_gke_hub_feature_membership.default["cluster-2"]',
+ 'module.hub.google_gke_hub_feature_membership.servicemesh["cluster-1"]',
+ 'module.hub.google_gke_hub_feature_membership.servicemesh["cluster-2"]',
'module.hub.google_gke_hub_membership.default["cluster-1"]',
'module.hub.google_gke_hub_membership.default["cluster-2"]'
]
@@ -58,6 +61,7 @@ def test_configmanagement_setup(resources):
'sync_wait_secs':
None
}],
+ 'oci': [],
'prevent_drift': False,
'source_format': 'hierarchy'
}],
diff --git a/tests/modules/gke_nodepool/examples/basic.yaml b/tests/modules/gke_nodepool/examples/basic.yaml
new file mode 100644
index 000000000..010b98cda
--- /dev/null
+++ b/tests/modules/gke_nodepool/examples/basic.yaml
@@ -0,0 +1,23 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.cluster-1-nodepool-1.google_container_node_pool.nodepool:
+ cluster: cluster-1
+ location: europe-west1-b
+ name: nodepool-1
+ project: myproject
+
+counts:
+ google_container_node_pool: 1
diff --git a/tests/modules/gke_nodepool/examples/config.yaml b/tests/modules/gke_nodepool/examples/config.yaml
new file mode 100644
index 000000000..858e5ca58
--- /dev/null
+++ b/tests/modules/gke_nodepool/examples/config.yaml
@@ -0,0 +1,59 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.cluster-1-nodepool-1.google_container_node_pool.nodepool:
+ autoscaling:
+ - max_node_count: 10
+ min_node_count: 1
+ total_max_node_count: null
+ total_min_node_count: null
+ cluster: cluster-1
+ initial_node_count: 1
+ location: europe-west1-b
+ management:
+ - auto_repair: true
+ auto_upgrade: false
+ name: nodepool-1
+ node_config:
+ - boot_disk_kms_key: null
+ disk_size_gb: 50
+ disk_type: pd-ssd
+ ephemeral_storage_config:
+ - local_ssd_count: 1
+ gcfs_config: []
+ gvnic: []
+ kubelet_config: []
+ labels:
+ environment: dev
+ linux_node_config: []
+ logging_variant: DEFAULT
+ machine_type: n2-standard-2
+ node_group: null
+ oauth_scopes:
+ - https://www.googleapis.com/auth/cloud-platform
+ preemptible: false
+ reservation_affinity: []
+ resource_labels: null
+ sandbox_config: []
+ spot: true
+ tags: null
+ taint: []
+ placement_policy: []
+ project: myproject
+ module.cluster-1-nodepool-1.google_service_account.service_account[0]: {}
+
+counts:
+ google_container_node_pool: 1
+ google_service_account: 1
diff --git a/tests/modules/gke_nodepool/examples/create-sa.yaml b/tests/modules/gke_nodepool/examples/create-sa.yaml
new file mode 100644
index 000000000..df1f2f708
--- /dev/null
+++ b/tests/modules/gke_nodepool/examples/create-sa.yaml
@@ -0,0 +1,52 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.cluster-1-nodepool-1.google_container_node_pool.nodepool:
+ cluster: cluster-1
+ location: europe-west1-b
+ name: nodepool-1
+ node_config:
+ - boot_disk_kms_key: null
+ disk_type: pd-balanced
+ ephemeral_storage_config: []
+ gcfs_config: []
+ gvnic: []
+ kubelet_config: []
+ linux_node_config: []
+ logging_variant: DEFAULT
+ node_group: null
+ oauth_scopes:
+ - https://www.googleapis.com/auth/cloud-platform
+ preemptible: false
+ reservation_affinity: []
+ resource_labels: null
+ sandbox_config: []
+ spot: false
+ tags: null
+ taint: []
+ placement_policy: []
+ project: myproject
+ timeouts: null
+ module.cluster-1-nodepool-1.google_service_account.service_account[0]:
+ account_id: spam-eggs
+ description: null
+ disabled: false
+ display_name: Terraform GKE cluster-1 nodepool-1.
+ project: myproject
+ timeouts: null
+
+counts:
+ google_container_node_pool: 1
+ google_service_account: 1
diff --git a/tests/modules/gke_nodepool/examples/external-sa.yaml b/tests/modules/gke_nodepool/examples/external-sa.yaml
new file mode 100644
index 000000000..059593215
--- /dev/null
+++ b/tests/modules/gke_nodepool/examples/external-sa.yaml
@@ -0,0 +1,43 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.cluster-1-nodepool-1.google_container_node_pool.nodepool:
+ cluster: cluster-1
+ location: europe-west1-b
+ name: nodepool-1
+ node_config:
+ - boot_disk_kms_key: null
+ disk_type: pd-balanced
+ ephemeral_storage_config: []
+ gcfs_config: []
+ gvnic: []
+ kubelet_config: []
+ linux_node_config: []
+ logging_variant: DEFAULT
+ node_group: null
+ oauth_scopes:
+ - https://www.googleapis.com/auth/cloud-platform
+ preemptible: false
+ reservation_affinity: []
+ resource_labels: null
+ sandbox_config: []
+ service_account: foo-bar@myproject.iam.gserviceaccount.com
+ spot: false
+ tags: null
+ taint: []
+ project: myproject
+
+counts:
+ google_container_node_pool: 1
diff --git a/tests/modules/gke_nodepool/fixture/main.tf b/tests/modules/gke_nodepool/fixture/main.tf
deleted file mode 100644
index 4ee274828..000000000
--- a/tests/modules/gke_nodepool/fixture/main.tf
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * 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.
- */
-
-resource "google_service_account" "test" {
- project = "my-project"
- account_id = "gke-nodepool-test"
- display_name = "Test Service Account"
-}
-
-module "test" {
- source = "../../../../modules/gke-nodepool"
- project_id = "my-project"
- cluster_name = "cluster-1"
- location = "europe-west1-b"
- name = "nodepool-1"
- gke_version = var.gke_version
- labels = var.labels
- max_pods_per_node = var.max_pods_per_node
- node_config = var.node_config
- node_count = var.node_count
- node_locations = var.node_locations
- nodepool_config = var.nodepool_config
- pod_range = var.pod_range
- reservation_affinity = var.reservation_affinity
- service_account = {
- create = var.service_account_create
- email = google_service_account.test.email
- }
- sole_tenant_nodegroup = var.sole_tenant_nodegroup
- tags = var.tags
- taints = var.taints
-}
diff --git a/tests/modules/gke_nodepool/fixture/variables.tf b/tests/modules/gke_nodepool/fixture/variables.tf
deleted file mode 100644
index 18376ec53..000000000
--- a/tests/modules/gke_nodepool/fixture/variables.tf
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * 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 "gke_version" {
- type = string
- default = null
-}
-
-variable "labels" {
- type = map(string)
- default = {}
- nullable = false
-}
-
-variable "max_pods_per_node" {
- type = number
- default = null
-}
-
-variable "node_config" {
- type = any
- default = {
- disk_type = "pd-balanced"
- }
-}
-
-variable "node_count" {
- type = any
- default = {
- initial = 1
- }
- nullable = false
-}
-
-variable "node_locations" {
- type = list(string)
- default = null
-}
-
-variable "nodepool_config" {
- type = any
- default = null
-}
-
-variable "pod_range" {
- type = any
- default = null
-}
-
-variable "reservation_affinity" {
- type = any
- default = null
-}
-
-variable "service_account_create" {
- type = bool
- default = false
-}
-
-variable "sole_tenant_nodegroup" {
- type = string
- default = null
-}
-
-variable "tags" {
- type = list(string)
- default = null
-}
-
-variable "taints" {
- type = any
- default = null
-}
diff --git a/tests/modules/gke_nodepool/test_plan.py b/tests/modules/gke_nodepool/test_plan.py
deleted file mode 100644
index 75d1cc14b..000000000
--- a/tests/modules/gke_nodepool/test_plan.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# 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_defaults(plan_runner):
- "Test resources created with variable defaults."
- _, resources = plan_runner()
- assert len(resources) == 1
- assert resources[0]['values']['autoscaling'] == []
-
-
-def test_service_account(plan_runner):
- _, resources = plan_runner()
- assert len(resources) == 1
- _, resources = plan_runner(service_account_create='true')
- assert len(resources) == 2
- assert 'google_service_account' in [r['type'] for r in resources]
-
-
-def test_nodepool_config(plan_runner):
- nodepool_config = '''{
- autoscaling = { use_total_nodes = true, max_node_count = 3}
- management = {}
- upgrade_settings = { max_surge = 3, max_unavailable = 3 }
- }'''
- _, resources = plan_runner(nodepool_config=nodepool_config)
- assert resources[0]['values']['autoscaling'] == [{
- 'location_policy': None,
- 'max_node_count': None,
- 'min_node_count': None,
- 'total_max_node_count': 3,
- 'total_min_node_count': None
- }]
- nodepool_config = '{ autoscaling = { max_node_count = 3} }'
- _, resources = plan_runner(nodepool_config=nodepool_config)
- assert resources[0]['values']['autoscaling'] == [{
- 'location_policy': None,
- 'max_node_count': 3,
- 'min_node_count': None,
- 'total_max_node_count': None,
- 'total_min_node_count': None
- }]
-
-
-def test_node_config(plan_runner):
- node_config = '''{
- gcfs = true
- metadata = { foo = "bar" }
- }'''
- _, resources = plan_runner(node_config=node_config)
- values = resources[0]['values']['node_config'][0]
- assert values['gcfs_config'] == [{'enabled': True}]
- assert values['metadata'] == {
- 'disable-legacy-endpoints': 'true',
- 'foo': 'bar'
- }
diff --git a/tests/modules/iam_service_account/examples/basic.yaml b/tests/modules/iam_service_account/examples/basic.yaml
new file mode 100644
index 000000000..4acc58519
--- /dev/null
+++ b/tests/modules/iam_service_account/examples/basic.yaml
@@ -0,0 +1,39 @@
+# 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.
+values:
+ module.myproject-default-service-accounts.google_project_iam_member.project-roles["myproject-roles/logging.logWriter"]:
+ condition: []
+ project: myproject
+ role: roles/logging.logWriter
+ module.myproject-default-service-accounts.google_project_iam_member.project-roles["myproject-roles/monitoring.metricWriter"]:
+ condition: []
+ project: myproject
+ role: roles/monitoring.metricWriter
+ module.myproject-default-service-accounts.google_service_account.service_account[0]:
+ account_id: vm-default
+ description: null
+ disabled: false
+ display_name: Terraform-managed.
+ project: myproject
+ timeouts: null
+ module.myproject-default-service-accounts.google_service_account_iam_binding.roles["roles/iam.serviceAccountUser"]:
+ condition: []
+ members:
+ - user:foo@example.com
+ role: roles/iam.serviceAccountUser
+
+counts:
+ google_project_iam_member: 2
+ google_service_account: 1
+ google_service_account_iam_binding: 1
diff --git a/tests/modules/iam_service_account/fixture/main.tf b/tests/modules/iam_service_account/fixture/main.tf
deleted file mode 100644
index 535139836..000000000
--- a/tests/modules/iam_service_account/fixture/main.tf
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * 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 "test" {
- source = "../../../../modules/iam-service-account"
- project_id = var.project_id
- name = "sa-one"
- prefix = var.prefix
- generate_key = var.generate_key
- iam = var.iam
- iam_billing_roles = var.iam_billing_roles
- iam_folder_roles = var.iam_folder_roles
- iam_organization_roles = var.iam_organization_roles
- iam_project_roles = var.iam_project_roles
- iam_storage_roles = var.iam_storage_roles
-}
diff --git a/tests/modules/iam_service_account/fixture/variables.tf b/tests/modules/iam_service_account/fixture/variables.tf
deleted file mode 100644
index 0a4781e07..000000000
--- a/tests/modules/iam_service_account/fixture/variables.tf
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * 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 "generate_key" {
- type = bool
- default = false
-}
-
-variable "iam" {
- type = map(list(string))
- default = {}
-}
-
-variable "iam_billing_roles" {
- type = map(list(string))
- default = {}
-}
-
-variable "iam_folder_roles" {
- type = map(list(string))
- default = {}
-}
-
-variable "iam_organization_roles" {
- type = map(list(string))
- default = {}
-}
-
-variable "iam_project_roles" {
- type = map(list(string))
- default = {}
-}
-
-variable "iam_storage_roles" {
- type = map(list(string))
- default = {}
-}
-
-variable "prefix" {
- type = string
- default = null
-}
-
-variable "project_id" {
- type = string
- default = "my-project"
-}
diff --git a/tests/modules/iam_service_account/test_plan.py b/tests/modules/iam_service_account/test_plan.py
deleted file mode 100644
index ff7865b5b..000000000
--- a/tests/modules/iam_service_account/test_plan.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# 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_resources(plan_runner):
- "Test service account resource."
- _, resources = plan_runner()
- assert len(resources) == 1
- resource = resources[0]
- assert resource['type'] == 'google_service_account'
- assert resource['values']['account_id'] == 'sa-one'
-
- _, resources = plan_runner(prefix='foo')
- assert len(resources) == 1
- resource = resources[0]
- assert resource['values']['account_id'] == 'foo-sa-one'
-
-
-def test_iam_roles(plan_runner):
- "Test iam roles with one member."
- iam = ('{"roles/iam.serviceAccountUser" = ["user:a@b.com"]}')
- _, resources = plan_runner(iam=iam)
- assert len(resources) == 2
- iam_resources = [r for r in resources
- if r['type'] != 'google_service_account']
- assert len(iam_resources) == 1
-
- iam_resource = iam_resources[0]
- assert iam_resource['type'] == 'google_service_account_iam_binding'
- assert iam_resource['index'] == 'roles/iam.serviceAccountUser'
- assert iam_resource['values']['role'] == 'roles/iam.serviceAccountUser'
- assert iam_resource['values']['members'] == ["user:a@b.com"]
diff --git a/tests/modules/logging_bucket/test_plan.py b/tests/modules/logging_bucket/test_plan.py
index 6c309e340..8ec685add 100644
--- a/tests/modules/logging_bucket/test_plan.py
+++ b/tests/modules/logging_bucket/test_plan.py
@@ -12,16 +12,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+
def test_project_logging_bucket(plan_runner):
"Test project logging bucket."
- _, resources = plan_runner(parent_type="project",
- parent="myproject")
+ _, resources = plan_runner(parent_type="project", parent="myproject")
assert len(resources) == 1
resource = resources[0]
assert resource["type"] == "google_logging_project_bucket_config"
assert resource["values"] == {
"bucket_id": "mybucket",
+ "cmek_settings": [],
"project": "myproject",
"location": "global",
"retention_days": 30,
@@ -30,15 +31,14 @@ def test_project_logging_bucket(plan_runner):
def test_folder_logging_bucket(plan_runner):
"Test project logging bucket."
- _, resources = plan_runner(
- parent_type="folder", parent="folders/0123456789"
- )
+ _, resources = plan_runner(parent_type="folder", parent="folders/0123456789")
assert len(resources) == 1
resource = resources[0]
assert resource["type"] == "google_logging_folder_bucket_config"
assert resource["values"] == {
"bucket_id": "mybucket",
+ "cmek_settings": [],
"folder": "folders/0123456789",
"location": "global",
"retention_days": 30,
@@ -47,15 +47,15 @@ def test_folder_logging_bucket(plan_runner):
def test_organization_logging_bucket(plan_runner):
"Test project logging bucket."
- _, resources = plan_runner(
- parent_type="organization", parent="organizations/0123456789"
- )
+ _, resources = plan_runner(parent_type="organization",
+ parent="organizations/0123456789")
assert len(resources) == 1
resource = resources[0]
assert resource["type"] == "google_logging_organization_bucket_config"
assert resource["values"] == {
"bucket_id": "mybucket",
+ "cmek_settings": [],
"organization": "organizations/0123456789",
"location": "global",
"retention_days": 30,
@@ -64,15 +64,14 @@ def test_organization_logging_bucket(plan_runner):
def test_billing_account_logging_bucket(plan_runner):
"Test project logging bucket."
- _, resources = plan_runner(
- parent_type="billing_account", parent="0123456789"
- )
+ _, resources = plan_runner(parent_type="billing_account", parent="0123456789")
assert len(resources) == 1
resource = resources[0]
assert resource["type"] == "google_logging_billing_account_bucket_config"
assert resource["values"] == {
"bucket_id": "mybucket",
+ "cmek_settings": [],
"billing_account": "0123456789",
"location": "global",
"retention_days": 30,
diff --git a/tests/modules/net_glb/examples/https-sneg.yaml b/tests/modules/net_glb/examples/https-sneg.yaml
new file mode 100644
index 000000000..fa0823cbf
--- /dev/null
+++ b/tests/modules/net_glb/examples/https-sneg.yaml
@@ -0,0 +1,35 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.glb-0.google_compute_backend_service.default["default"]:
+ port_name: http
+ protocol: HTTPS
+ module.glb-0.google_compute_global_forwarding_rule.default:
+ load_balancing_scheme: EXTERNAL
+ port_range: '443'
+ module.glb-0.google_compute_region_network_endpoint_group.serverless["neg-0"]:
+ cloud_run:
+ - service: hello
+ tag: null
+ url_mask: null
+
+counts:
+ google_compute_backend_service: 1
+ google_compute_global_forwarding_rule: 1
+ google_compute_managed_ssl_certificate: 1
+ google_compute_region_network_endpoint_group: 1
+ google_compute_target_https_proxy: 1
+ google_compute_url_map: 1
+
diff --git a/tests/modules/net_glb/test-plan.tfvars b/tests/modules/net_glb/test-plan.tfvars
index f10667f17..94cc5ab2a 100644
--- a/tests/modules/net_glb/test-plan.tfvars
+++ b/tests/modules/net_glb/test-plan.tfvars
@@ -62,30 +62,36 @@ neg_configs = {
network = "projects/my-project/global/networks/shared-vpc"
subnetwork = "projects/my-project/regions/europe-west8/subnetworks/gce"
zone = "europe-west8-b"
- endpoints = [{
- instance = "nginx-ew8-b"
- ip_address = "10.24.32.25"
- port = 80
- }]
+ endpoints = {
+ e-0 = {
+ instance = "nginx-ew8-b"
+ ip_address = "10.24.32.25"
+ port = 80
+ }
+ }
}
}
neg-hybrid = {
hybrid = {
network = "projects/my-project/global/networks/shared-vpc"
zone = "europe-west8-b"
- endpoints = [{
- ip_address = "192.168.0.3"
- port = 80
- }]
+ endpoints = {
+ e-0 = {
+ ip_address = "192.168.0.3"
+ port = 80
+ }
+ }
}
}
neg-internet = {
internet = {
use_fqdn = true
- endpoints = [{
- destination = "hello.example.org"
- port = 80
- }]
+ endpoints = {
+ e-0 = {
+ destination = "hello.example.org"
+ port = 80
+ }
+ }
}
}
}
diff --git a/tests/modules/net_ilb_l7/fixture/test.negs.tfvars b/tests/modules/net_ilb_l7/fixture/test.negs.tfvars
index 2f7f48d57..f6141a7ef 100644
--- a/tests/modules/net_ilb_l7/fixture/test.negs.tfvars
+++ b/tests/modules/net_ilb_l7/fixture/test.negs.tfvars
@@ -9,11 +9,13 @@ neg_configs = {
custom = {
gce = {
zone = "europe-west1-b"
- endpoints = [{
- ip_address = "10.0.0.10"
- instance = "test-1"
- port = 80
- }]
+ endpoints = {
+ e-0 = {
+ ip_address = "10.0.0.10"
+ instance = "test-1"
+ port = 80
+ }
+ }
}
}
}
diff --git a/tests/modules/net_vpc/data/factory-subnet.yaml b/tests/modules/net_vpc/data/factory-subnet.yaml
deleted file mode 100644
index d0f4bd8f1..000000000
--- a/tests/modules/net_vpc/data/factory-subnet.yaml
+++ /dev/null
@@ -1,23 +0,0 @@
-# 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.
-
-region: europe-west1
-description: Sample description
-ip_cidr_range: 10.128.0.0/24
-enable_private_access: false
-iam_users: ["foobar@example.com"]
-iam_groups: ["lorem@example.com"]
-iam_service_accounts: ["foobar@project-id.iam.gserviceaccount.com"]
-secondary_ip_ranges:
- secondary-range-a: 192.168.128.0/24
diff --git a/tests/modules/net_vpc/examples/dns-policies.yaml b/tests/modules/net_vpc/examples/dns-policies.yaml
new file mode 100644
index 000000000..a30d6408a
--- /dev/null
+++ b/tests/modules/net_vpc/examples/dns-policies.yaml
@@ -0,0 +1,42 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vpc.google_compute_network.network[0]:
+ name: my-network
+ project: my-project
+ module.vpc.google_compute_subnetwork.subnetwork["europe-west1/production"]: {}
+ module.vpc.google_dns_policy.default[0]:
+ alternative_name_server_config:
+ - target_name_servers:
+ - forwarding_path: ''
+ ipv4_address: '8.8.8.8'
+ - forwarding_path: private
+ ipv4_address: '10.0.0.1'
+ description: Managed by Terraform
+ enable_inbound_forwarding: true
+ enable_logging: null
+ name: my-network
+ networks:
+ - {}
+ project: my-project
+
+counts:
+ google_compute_network: 1
+ google_compute_subnetwork: 1
+ google_dns_policy: 1
+ modules: 1
+ resources: 3
+
+outputs: {}
diff --git a/tests/modules/net_vpc/examples/factory.yaml b/tests/modules/net_vpc/examples/factory.yaml
new file mode 100644
index 000000000..48671c292
--- /dev/null
+++ b/tests/modules/net_vpc/examples/factory.yaml
@@ -0,0 +1,50 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vpc.google_compute_network.network[0]:
+ name: my-network
+ project: my-project
+ routing_mode: GLOBAL
+ module.vpc.google_compute_subnetwork.subnetwork["europe-west1/subnet-detailed"]:
+ description: Sample description
+ ip_cidr_range: 10.0.0.0/24
+ log_config:
+ - aggregation_interval: INTERVAL_5_SEC
+ filter_expr: 'true'
+ flow_sampling: 0.5
+ metadata: INCLUDE_ALL_METADATA
+ metadata_fields: null
+ name: subnet-detailed
+ private_ip_google_access: false
+ project: my-project
+ region: europe-west1
+ role: null
+ secondary_ip_range:
+ - ip_cidr_range: 192.168.0.0/24
+ range_name: secondary-range-a
+ module.vpc.google_compute_subnetwork.subnetwork["europe-west4/subnet-simple"]:
+ description: Terraform-managed.
+ ip_cidr_range: 10.0.1.0/24
+ log_config: []
+ name: subnet-simple
+ private_ip_google_access: true
+ project: my-project
+ region: europe-west4
+ role: null
+ secondary_ip_range: []
+
+counts:
+ google_compute_network: 1
+ google_compute_subnetwork: 2
diff --git a/tests/modules/net_vpc/peering.yaml b/tests/modules/net_vpc/examples/peering.yaml
similarity index 50%
rename from tests/modules/net_vpc/peering.yaml
rename to tests/modules/net_vpc/examples/peering.yaml
index 8d0bbed71..937ce1445 100644
--- a/tests/modules/net_vpc/peering.yaml
+++ b/tests/modules/net_vpc/examples/peering.yaml
@@ -1,4 +1,4 @@
-# Copyright 2022 Google LLC
+# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,35 +13,22 @@
# limitations under the License.
values:
- google_compute_network.network[0]:
- auto_create_subnetworks: false
- delete_default_routes_on_create: false
- description: Terraform-managed.
- name: test
- project: test-project
- routing_mode: GLOBAL
- google_compute_network_peering.local[0]:
- export_custom_routes: true
- import_custom_routes: false
- name: test-peer
- peer_network: projects/my-project/global/networks/peer
- google_compute_network_peering.remote[0]:
+ module.vpc-hub.google_compute_network.network[0]: {}
+ module.vpc-spoke-1.google_compute_network.network[0]: {}
+ module.vpc-hub.google_compute_subnetwork.subnetwork["europe-west1/subnet-1"]: {}
+ module.vpc-spoke-1.google_compute_subnetwork.subnetwork["europe-west1/subnet-2"]: {}
+ module.vpc-spoke-1.google_compute_network_peering.local[0]:
export_custom_routes: false
+ export_subnet_routes_with_public_ip: true
import_custom_routes: true
- name: peer-test
- network: projects/my-project/global/networks/peer
+ import_subnet_routes_with_public_ip: null
+ module.vpc-spoke-1.google_compute_network_peering.remote[0]:
+ export_custom_routes: true
+ export_subnet_routes_with_public_ip: true
+ import_custom_routes: false
+ import_subnet_routes_with_public_ip: null
counts:
- google_compute_network: 1
+ google_compute_network: 2
google_compute_network_peering: 2
-
-outputs:
- bindings: {}
- project_id: test-project
- subnet_ips: {}
- subnet_regions: {}
- subnet_secondary_ranges: {}
- subnet_self_links: {}
- subnets: {}
- subnets_proxy_only: {}
- subnets_psc: {}
+ google_compute_subnetwork: 2
diff --git a/tests/modules/net_vpc/examples/proxy-only-subnets.yaml b/tests/modules/net_vpc/examples/proxy-only-subnets.yaml
new file mode 100644
index 000000000..6e2069aaa
--- /dev/null
+++ b/tests/modules/net_vpc/examples/proxy-only-subnets.yaml
@@ -0,0 +1,40 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vpc.google_compute_network.network[0]:
+ name: my-network
+ project: my-project
+ module.vpc.google_compute_subnetwork.proxy_only["europe-west1/regional-proxy"]:
+ description: Terraform-managed proxy-only subnet for Regional HTTPS or Internal HTTPS LB.
+ ip_cidr_range: 10.0.1.0/24
+ log_config: []
+ name: regional-proxy
+ project: my-project
+ purpose: REGIONAL_MANAGED_PROXY
+ region: europe-west1
+ role: ACTIVE
+ module.vpc.google_compute_subnetwork.psc["europe-west1/psc"]:
+ description: Terraform-managed subnet for Private Service Connect (PSC NAT).
+ ip_cidr_range: 10.0.3.0/24
+ log_config: []
+ name: psc
+ project: my-project
+ purpose: PRIVATE_SERVICE_CONNECT
+ region: europe-west1
+ role: null
+
+counts:
+ google_compute_network: 1
+ google_compute_subnetwork: 2
diff --git a/tests/modules/net_vpc/examples/psc-routes.yaml b/tests/modules/net_vpc/examples/psc-routes.yaml
new file mode 100644
index 000000000..6f459f4b7
--- /dev/null
+++ b/tests/modules/net_vpc/examples/psc-routes.yaml
@@ -0,0 +1,47 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vpc.google_compute_global_address.psa_ranges["myrange"]:
+ address: 10.0.1.0
+ address_type: INTERNAL
+ description: null
+ ip_version: null
+ name: myrange
+ prefix_length: 24
+ project: my-project
+ purpose: VPC_PEERING
+ module.vpc.google_compute_network.network[0]:
+ name: my-network
+ project: my-project
+ routing_mode: GLOBAL
+ module.vpc.google_compute_network_peering_routes_config.psa_routes["1"]:
+ export_custom_routes: true
+ import_custom_routes: true
+ project: my-project
+ module.vpc.google_compute_subnetwork.subnetwork["europe-west1/production"]:
+ ip_cidr_range: 10.0.0.0/24
+ name: production
+ project: my-project
+ module.vpc.google_service_networking_connection.psa_connection["1"]:
+ reserved_peering_ranges:
+ - myrange
+ service: servicenetworking.googleapis.com
+
+counts:
+ google_compute_global_address: 1
+ google_compute_network: 1
+ google_compute_network_peering_routes_config: 1
+ google_compute_subnetwork: 1
+ google_service_networking_connection: 1
diff --git a/tests/modules/net_vpc/examples/psc.yaml b/tests/modules/net_vpc/examples/psc.yaml
new file mode 100644
index 000000000..c08fcb453
--- /dev/null
+++ b/tests/modules/net_vpc/examples/psc.yaml
@@ -0,0 +1,46 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vpc.google_compute_global_address.psa_ranges["myrange"]:
+ address: 10.0.1.0
+ address_type: INTERNAL
+ name: myrange
+ prefix_length: 24
+ project: my-project
+ purpose: VPC_PEERING
+ module.vpc.google_compute_network.network[0]:
+ name: my-network
+ project: my-project
+ module.vpc.google_compute_network_peering_routes_config.psa_routes["1"]:
+ export_custom_routes: false
+ import_custom_routes: false
+ project: my-project
+ module.vpc.google_compute_subnetwork.subnetwork["europe-west1/production"]:
+ ip_cidr_range: 10.0.0.0/24
+ name: production
+ project: my-project
+ module.vpc.google_service_networking_connection.psa_connection["1"]:
+ reserved_peering_ranges:
+ - myrange
+ service: servicenetworking.googleapis.com
+
+counts:
+ google_compute_global_address: 1
+ google_compute_network: 1
+ google_compute_network_peering_routes_config: 1
+ google_compute_subnetwork: 1
+ google_service_networking_connection: 1
+
+outputs: {}
diff --git a/tests/modules/net_vpc/examples/routes.yaml b/tests/modules/net_vpc/examples/routes.yaml
new file mode 100644
index 000000000..205197c82
--- /dev/null
+++ b/tests/modules/net_vpc/examples/routes.yaml
@@ -0,0 +1,146 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vpc["gateway"].google_compute_network.network[0]:
+ name: my-network-with-route-gateway
+ project: my-project
+ routing_mode: GLOBAL
+ module.vpc["gateway"].google_compute_route.gateway["gateway"]:
+ dest_range: 0.0.0.0/0
+ name: my-network-with-route-gateway-gateway
+ next_hop_gateway: global/gateways/default-internet-gateway
+ next_hop_ilb: null
+ next_hop_instance: null
+ next_hop_vpn_tunnel: null
+ priority: 100
+ project: my-project
+ tags:
+ - tag-a
+ module.vpc["gateway"].google_compute_route.gateway["next-hop"]:
+ dest_range: 192.168.128.0/24
+ name: my-network-with-route-gateway-next-hop
+ next_hop_gateway: global/gateways/default-internet-gateway
+ next_hop_ilb: null
+ next_hop_instance: null
+ next_hop_vpn_tunnel: null
+ priority: 1000
+ project: my-project
+ tags: null
+ module.vpc["ilb"].google_compute_network.network[0]:
+ name: my-network-with-route-ilb
+ project: my-project
+ routing_mode: GLOBAL
+ module.vpc["ilb"].google_compute_route.gateway["gateway"]:
+ dest_range: 0.0.0.0/0
+ name: my-network-with-route-ilb-gateway
+ next_hop_gateway: global/gateways/default-internet-gateway
+ next_hop_ilb: null
+ next_hop_instance: null
+ next_hop_vpn_tunnel: null
+ priority: 100
+ project: my-project
+ tags:
+ - tag-a
+ module.vpc["ilb"].google_compute_route.ilb["next-hop"]:
+ dest_range: 192.168.128.0/24
+ name: my-network-with-route-ilb-next-hop
+ next_hop_gateway: null
+ next_hop_ilb: regions/europe-west1/forwardingRules/test
+ next_hop_instance: null
+ next_hop_vpn_tunnel: null
+ priority: 1000
+ project: my-project
+ tags: null
+ module.vpc["instance"].google_compute_network.network[0]:
+ name: my-network-with-route-instance
+ project: my-project
+ routing_mode: GLOBAL
+ module.vpc["instance"].google_compute_route.gateway["gateway"]:
+ dest_range: 0.0.0.0/0
+ name: my-network-with-route-instance-gateway
+ next_hop_gateway: global/gateways/default-internet-gateway
+ next_hop_ilb: null
+ next_hop_instance: null
+ next_hop_vpn_tunnel: null
+ priority: 100
+ project: my-project
+ tags:
+ - tag-a
+ module.vpc["instance"].google_compute_route.instance["next-hop"]:
+ dest_range: 192.168.128.0/24
+ name: my-network-with-route-instance-next-hop
+ next_hop_gateway: null
+ next_hop_ilb: null
+ next_hop_instance: zones/europe-west1-b/test
+ next_hop_instance_zone: europe-west1-b
+ next_hop_vpn_tunnel: null
+ priority: 1000
+ project: my-project
+ tags: null
+ module.vpc["ip"].google_compute_network.network[0]:
+ name: my-network-with-route-ip
+ project: my-project
+ routing_mode: GLOBAL
+ module.vpc["ip"].google_compute_route.gateway["gateway"]:
+ dest_range: 0.0.0.0/0
+ name: my-network-with-route-ip-gateway
+ next_hop_gateway: global/gateways/default-internet-gateway
+ next_hop_ilb: null
+ next_hop_instance: null
+ next_hop_vpn_tunnel: null
+ priority: 100
+ project: my-project
+ tags:
+ - tag-a
+ module.vpc["ip"].google_compute_route.ip["next-hop"]:
+ dest_range: 192.168.128.0/24
+ name: my-network-with-route-ip-next-hop
+ next_hop_gateway: null
+ next_hop_ilb: null
+ next_hop_instance: null
+ next_hop_ip: 192.168.0.128
+ next_hop_vpn_tunnel: null
+ priority: 1000
+ project: my-project
+ tags: null
+ module.vpc["vpn_tunnel"].google_compute_network.network[0]:
+ name: my-network-with-route-vpn-tunnel
+ project: my-project
+ routing_mode: GLOBAL
+ module.vpc["vpn_tunnel"].google_compute_route.gateway["gateway"]:
+ dest_range: 0.0.0.0/0
+ name: my-network-with-route-vpn-tunnel-gateway
+ next_hop_gateway: global/gateways/default-internet-gateway
+ next_hop_ilb: null
+ next_hop_instance: null
+ next_hop_vpn_tunnel: null
+ priority: 100
+ project: my-project
+ tags:
+ - tag-a
+ module.vpc["vpn_tunnel"].google_compute_route.vpn_tunnel["next-hop"]:
+ dest_range: 192.168.128.0/24
+ name: my-network-with-route-vpn-tunnel-next-hop
+ next_hop_gateway: null
+ next_hop_ilb: null
+ next_hop_instance: null
+ next_hop_vpn_tunnel: regions/europe-west1/vpnTunnels/foo
+ priority: 1000
+ project: my-project
+ tags: null
+
+counts:
+ google_compute_network: 5
+ google_compute_route: 10
diff --git a/tests/modules/net_vpc/examples/shared-vpc.yaml b/tests/modules/net_vpc/examples/shared-vpc.yaml
new file mode 100644
index 000000000..b004e3151
--- /dev/null
+++ b/tests/modules/net_vpc/examples/shared-vpc.yaml
@@ -0,0 +1,51 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vpc-host.google_compute_network.network[0]:
+ name: my-host-network
+ project: my-project
+ module.vpc-host.google_compute_shared_vpc_host_project.shared_vpc_host[0]:
+ project: my-project
+ module.vpc-host.google_compute_shared_vpc_service_project.service_projects["project1"]:
+ host_project: my-project
+ service_project: project1
+ module.vpc-host.google_compute_shared_vpc_service_project.service_projects["project2"]:
+ host_project: my-project
+ service_project: project2
+ module.vpc-host.google_compute_subnetwork.subnetwork["europe-west1/subnet-1"]: {}
+ module.vpc-host.google_compute_subnetwork_iam_binding.binding["europe-west1/subnet-1.roles/compute.networkUser"]:
+ condition: []
+ members:
+ - serviceAccount:cloudsvc
+ - serviceAccount:gke
+ project: my-project
+ region: europe-west1
+ role: roles/compute.networkUser
+ subnetwork: subnet-1
+ module.vpc-host.google_compute_subnetwork_iam_binding.binding["europe-west1/subnet-1.roles/compute.securityAdmin"]:
+ condition: []
+ members:
+ - serviceAccount:gke
+ project: my-project
+ region: europe-west1
+ role: roles/compute.securityAdmin
+ subnetwork: subnet-1
+
+counts:
+ google_compute_network: 1
+ google_compute_shared_vpc_host_project: 1
+ google_compute_shared_vpc_service_project: 2
+ google_compute_subnetwork: 1
+ google_compute_subnetwork_iam_binding: 2
diff --git a/tests/modules/net_vpc/examples/simple.yaml b/tests/modules/net_vpc/examples/simple.yaml
new file mode 100644
index 000000000..799852c02
--- /dev/null
+++ b/tests/modules/net_vpc/examples/simple.yaml
@@ -0,0 +1,50 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vpc.google_compute_network.network[0]:
+ auto_create_subnetworks: false
+ delete_default_routes_on_create: false
+ description: Terraform-managed.
+ name: my-network
+ project: my-project
+ routing_mode: GLOBAL
+ module.vpc.google_compute_subnetwork.subnetwork["europe-west1/production"]:
+ description: Terraform-managed.
+ ip_cidr_range: 10.0.0.0/24
+ log_config: []
+ name: production
+ private_ip_google_access: true
+ project: my-project
+ region: europe-west1
+ role: null
+ secondary_ip_range:
+ - ip_cidr_range: 172.16.0.0/20
+ range_name: pods
+ - ip_cidr_range: 192.168.0.0/24
+ range_name: services
+ module.vpc.google_compute_subnetwork.subnetwork["europe-west2/production"]:
+ description: Terraform-managed.
+ ip_cidr_range: 10.0.16.0/24
+ log_config: []
+ name: production
+ private_ip_google_access: true
+ project: my-project
+ region: europe-west2
+ role: null
+ secondary_ip_range: []
+
+counts:
+ google_compute_network: 1
+ google_compute_subnetwork: 2
diff --git a/tests/modules/net_vpc/examples/subnet-iam.yaml b/tests/modules/net_vpc/examples/subnet-iam.yaml
new file mode 100644
index 000000000..cb53ecd80
--- /dev/null
+++ b/tests/modules/net_vpc/examples/subnet-iam.yaml
@@ -0,0 +1,54 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vpc.google_compute_network.network[0]:
+ name: my-network
+ project: my-project
+ module.vpc.google_compute_subnetwork.subnetwork["europe-west1/subnet-1"]:
+ name: subnet-1
+ project: my-project
+ region: europe-west1
+ module.vpc.google_compute_subnetwork.subnetwork["europe-west1/subnet-2"]:
+ name: subnet-2
+ private_ip_google_access: true
+ project: my-project
+ region: europe-west1
+ module.vpc.google_compute_subnetwork_iam_binding.binding["europe-west1/subnet-1.roles/compute.networkUser"]:
+ condition: []
+ members:
+ - group:group1@example.com
+ - user:user1@example.com
+ project: my-project
+ region: europe-west1
+ role: roles/compute.networkUser
+ subnetwork: subnet-1
+ module.vpc.google_compute_subnetwork_iam_binding.binding["europe-west1/subnet-2.roles/compute.networkUser"]:
+ condition: []
+ members:
+ - group:group2@example.com
+ - user:user2@example.com
+ project: my-project
+ region: europe-west1
+ role: roles/compute.networkUser
+ subnetwork: subnet-2
+
+counts:
+ google_compute_network: 1
+ google_compute_subnetwork: 2
+ google_compute_subnetwork_iam_binding: 2
+ modules: 1
+ resources: 5
+
+outputs: {}
diff --git a/tests/modules/net_vpc/examples/subnet-options.yaml b/tests/modules/net_vpc/examples/subnet-options.yaml
new file mode 100644
index 000000000..e3cea5ca6
--- /dev/null
+++ b/tests/modules/net_vpc/examples/subnet-options.yaml
@@ -0,0 +1,70 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.vpc.google_compute_network.network[0]:
+ name: my-network
+ project: my-project
+ module.vpc.google_compute_subnetwork.subnetwork["europe-west1/no-pga"]:
+ description: Subnet b
+ ip_cidr_range: 10.0.1.0/24
+ log_config: []
+ name: no-pga
+ private_ip_google_access: false
+ project: my-project
+ region: europe-west1
+ secondary_ip_range: []
+ module.vpc.google_compute_subnetwork.subnetwork["europe-west1/simple"]:
+ description: Terraform-managed.
+ ip_cidr_range: 10.0.0.0/24
+ log_config: []
+ name: simple
+ private_ip_google_access: true
+ project: my-project
+ region: europe-west1
+ secondary_ip_range: []
+ module.vpc.google_compute_subnetwork.subnetwork["europe-west1/with-flow-logs"]:
+ description: Terraform-managed.
+ ip_cidr_range: 10.0.3.0/24
+ ipv6_access_type: null
+ log_config:
+ - aggregation_interval: INTERVAL_10_MIN
+ filter_expr: 'true'
+ flow_sampling: 0.5
+ metadata: INCLUDE_ALL_METADATA
+ metadata_fields: null
+ name: with-flow-logs
+ private_ip_google_access: true
+ project: my-project
+ region: europe-west1
+ role: null
+ secondary_ip_range: []
+ module.vpc.google_compute_subnetwork.subnetwork["europe-west1/with-secondary-ranges"]:
+ description: Terraform-managed.
+ ip_cidr_range: 10.0.2.0/24
+ log_config: []
+ name: with-secondary-ranges
+ private_ip_google_access: true
+ project: my-project
+ region: europe-west1
+ role: null
+ secondary_ip_range:
+ - ip_cidr_range: 192.168.0.0/24
+ range_name: a
+ - ip_cidr_range: 192.168.1.0/24
+ range_name: b
+
+counts:
+ google_compute_network: 1
+ google_compute_subnetwork: 4
diff --git a/tests/modules/net_vpc/factory.tfvars b/tests/modules/net_vpc/factory.tfvars
deleted file mode 100644
index 8c4d4a28c..000000000
--- a/tests/modules/net_vpc/factory.tfvars
+++ /dev/null
@@ -1 +0,0 @@
-data_folder = "../../tests/modules/net_vpc/data"
diff --git a/tests/modules/net_vpc/factory.yaml b/tests/modules/net_vpc/factory.yaml
deleted file mode 100644
index 9cf628d09..000000000
--- a/tests/modules/net_vpc/factory.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-# 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.
-
-values:
- google_compute_subnetwork.subnetwork["europe-west1/factory-subnet"]:
- description: 'Sample description'
- ip_cidr_range: '10.128.0.0/24'
- ipv6_access_type: null
- log_config: []
- name: 'factory-subnet'
- private_ip_google_access: false
- project: 'test-project'
- region: 'europe-west1'
- role: null
- secondary_ip_range:
- - ip_cidr_range: '192.168.128.0/24'
- range_name: 'secondary-range-a'
- google_compute_subnetwork.subnetwork["europe-west4/factory-subnet2"]:
- description: 'Sample description'
- ip_cidr_range: '10.129.0.0/24'
- log_config: []
- name: 'factory-subnet2'
- private_ip_google_access: true
- project: 'test-project'
- region: 'europe-west4'
- role: null
- secondary_ip_range: []
-
- # FIXME: should we have some bindings here?
-
-counts:
- google_compute_network: 1
- google_compute_subnetwork: 2
diff --git a/tests/modules/net_vpc/fixture/main.tf b/tests/modules/net_vpc/fixture/main.tf
deleted file mode 100644
index f0e4696e0..000000000
--- a/tests/modules/net_vpc/fixture/main.tf
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * 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 "test" {
- source = "../../../../modules/net-vpc"
- project_id = "test-project"
- name = "test"
- peering_config = var.peering_config
- routes = var.routes
- shared_vpc_host = var.shared_vpc_host
- shared_vpc_service_projects = var.shared_vpc_service_projects
- subnet_iam = var.subnet_iam
- subnets = var.subnets
- auto_create_subnetworks = var.auto_create_subnetworks
- psa_config = var.psa_config
- data_folder = var.data_folder
-}
diff --git a/tests/modules/net_vpc/fixture/test.subnets.tfvars b/tests/modules/net_vpc/fixture/test.subnets.tfvars
deleted file mode 100644
index 499e498f4..000000000
--- a/tests/modules/net_vpc/fixture/test.subnets.tfvars
+++ /dev/null
@@ -1,44 +0,0 @@
-subnet_iam = {
- "europe-west1/a" = {
- "roles/compute.networkUser" = [
- "user:a@example.com", "group:g-a@example.com"
- ]
- }
- "europe-west1/c" = {
- "roles/compute.networkUser" = [
- "user:c@example.com", "group:g-c@example.com"
- ]
- }
-}
-subnets = [
- {
- name = "a"
- region = "europe-west1"
- ip_cidr_range = "10.0.0.0/24"
- },
- {
- name = "b"
- region = "europe-west1"
- ip_cidr_range = "10.0.1.0/24",
- description = "Subnet b"
- enable_private_access = false
- },
- {
- name = "c"
- region = "europe-west1"
- ip_cidr_range = "10.0.2.0/24"
- secondary_ip_ranges = {
- a = "192.168.0.0/24"
- b = "192.168.1.0/24"
- }
- },
- {
- name = "d"
- region = "europe-west1"
- ip_cidr_range = "10.0.3.0/24"
- flow_logs_config = {
- flow_sampling = 0.5
- aggregation_interval = "INTERVAL_10_MIN"
- }
- }
-]
diff --git a/tests/modules/net_vpc/fixture/variables.tf b/tests/modules/net_vpc/fixture/variables.tf
deleted file mode 100644
index 868966c8b..000000000
--- a/tests/modules/net_vpc/fixture/variables.tf
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * 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 "auto_create_subnetworks" {
- type = bool
- default = false
-}
-
-variable "data_folder" {
- type = string
- default = null
-}
-
-variable "delete_default_routes_on_create" {
- type = bool
- default = false
-}
-
-variable "description" {
- type = string
- default = "Terraform-managed."
-}
-
-variable "dns_policy" {
- type = any
- default = null
-}
-
-variable "mtu" {
- type = number
- default = null
-}
-
-variable "peering_config" {
- type = any
- default = null
-}
-
-variable "psa_config" {
- type = any
- default = null
-}
-
-variable "routes" {
- type = any
- default = {}
- nullable = false
-}
-
-variable "routing_mode" {
- type = string
- default = "GLOBAL"
-}
-
-variable "shared_vpc_host" {
- type = bool
- default = false
-}
-
-variable "shared_vpc_service_projects" {
- type = list(string)
- default = []
-}
-
-variable "subnets" {
- type = any
- default = []
-}
-
-variable "subnet_iam" {
- type = map(map(list(string)))
- default = {}
-}
-
-variable "subnets_proxy_only" {
- type = any
- default = []
-}
-
-variable "subnets_psc" {
- type = any
- default = []
-}
-
-variable "vpc_create" {
- type = bool
- default = true
-}
diff --git a/tests/modules/net_vpc/peering.tfvars b/tests/modules/net_vpc/peering.tfvars
deleted file mode 100644
index eccd7ae71..000000000
--- a/tests/modules/net_vpc/peering.tfvars
+++ /dev/null
@@ -1,5 +0,0 @@
-peering_config = {
- peer_vpc_self_link = "projects/my-project/global/networks/peer"
- export_routes = true
- import_routes = null
-}
diff --git a/tests/modules/net_vpc/psa_simple.tfvars b/tests/modules/net_vpc/psa_simple.tfvars
deleted file mode 100644
index 51289fe04..000000000
--- a/tests/modules/net_vpc/psa_simple.tfvars
+++ /dev/null
@@ -1,7 +0,0 @@
-psa_config = {
- ranges = {
- bar = "172.16.100.0/24"
- foo = "172.16.101.0/24"
- }
- routes = null
-}
diff --git a/tests/modules/net_vpc/psa_simple.yaml b/tests/modules/net_vpc/psa_simple.yaml
deleted file mode 100644
index 019b443fa..000000000
--- a/tests/modules/net_vpc/psa_simple.yaml
+++ /dev/null
@@ -1,70 +0,0 @@
-# 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.
-
-values:
- google_compute_global_address.psa_ranges["bar"]:
- address: 172.16.100.0
- address_type: INTERNAL
- description: null
- ip_version: null
- name: bar
- prefix_length: 24
- project: test-project
- purpose: VPC_PEERING
- google_compute_global_address.psa_ranges["foo"]:
- address: 172.16.101.0
- address_type: INTERNAL
- description: null
- ip_version: null
- name: foo
- prefix_length: 24
- project: test-project
- purpose: VPC_PEERING
- google_compute_network.network[0]:
- auto_create_subnetworks: false
- delete_default_routes_on_create: false
- description: Terraform-managed.
- enable_ula_internal_ipv6: null
- name: test
- project: test-project
- routing_mode: GLOBAL
- google_compute_network_peering_routes_config.psa_routes["1"]:
- export_custom_routes: false
- import_custom_routes: false
- project: test-project
- google_service_networking_connection.psa_connection["1"]:
- reserved_peering_ranges:
- - bar
- - foo
- service: servicenetworking.googleapis.com
-
-counts:
- google_compute_global_address: 2
- google_compute_network: 1
- google_compute_network_peering_routes_config: 1
- google_service_networking_connection: 1
-
-outputs:
- bindings: {}
- name: __missing__
- network: __missing__
- project_id: test-project
- self_link: __missing__
- subnet_ips: {}
- subnet_regions: {}
- subnet_secondary_ranges: {}
- subnet_self_links: {}
- subnets: {}
- subnets_proxy_only: {}
- subnets_psc: {}
diff --git a/tests/modules/net_vpc/simple.tfvars b/tests/modules/net_vpc/simple.tfvars
deleted file mode 100644
index 6f848aa99..000000000
--- a/tests/modules/net_vpc/simple.tfvars
+++ /dev/null
@@ -1 +0,0 @@
-# skip boilerplate check
diff --git a/tests/modules/net_vpc/subnets.tfvars b/tests/modules/net_vpc/subnets.tfvars
deleted file mode 100644
index 499e498f4..000000000
--- a/tests/modules/net_vpc/subnets.tfvars
+++ /dev/null
@@ -1,44 +0,0 @@
-subnet_iam = {
- "europe-west1/a" = {
- "roles/compute.networkUser" = [
- "user:a@example.com", "group:g-a@example.com"
- ]
- }
- "europe-west1/c" = {
- "roles/compute.networkUser" = [
- "user:c@example.com", "group:g-c@example.com"
- ]
- }
-}
-subnets = [
- {
- name = "a"
- region = "europe-west1"
- ip_cidr_range = "10.0.0.0/24"
- },
- {
- name = "b"
- region = "europe-west1"
- ip_cidr_range = "10.0.1.0/24",
- description = "Subnet b"
- enable_private_access = false
- },
- {
- name = "c"
- region = "europe-west1"
- ip_cidr_range = "10.0.2.0/24"
- secondary_ip_ranges = {
- a = "192.168.0.0/24"
- b = "192.168.1.0/24"
- }
- },
- {
- name = "d"
- region = "europe-west1"
- ip_cidr_range = "10.0.3.0/24"
- flow_logs_config = {
- flow_sampling = 0.5
- aggregation_interval = "INTERVAL_10_MIN"
- }
- }
-]
diff --git a/tests/modules/net_vpc/subnets.yaml b/tests/modules/net_vpc/subnets.yaml
deleted file mode 100644
index 9ccf31e60..000000000
--- a/tests/modules/net_vpc/subnets.yaml
+++ /dev/null
@@ -1,120 +0,0 @@
-# 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.
-
-values:
- google_compute_network.network[0]:
- auto_create_subnetworks: false
- delete_default_routes_on_create: false
- description: Terraform-managed.
- name: test
- project: test-project
- routing_mode: GLOBAL
- google_compute_subnetwork.subnetwork["europe-west1/a"]:
- description: Terraform-managed.
- ip_cidr_range: 10.0.0.0/24
- log_config: []
- name: a
- private_ip_google_access: true
- project: test-project
- region: europe-west1
- role: null
- secondary_ip_range: []
- google_compute_subnetwork.subnetwork["europe-west1/b"]:
- description: Subnet b
- ip_cidr_range: 10.0.1.0/24
- log_config: []
- name: b
- private_ip_google_access: false
- project: test-project
- region: europe-west1
- role: null
- secondary_ip_range: []
- google_compute_subnetwork.subnetwork["europe-west1/c"]:
- description: Terraform-managed.
- ip_cidr_range: 10.0.2.0/24
- ipv6_access_type: null
- log_config: []
- name: c
- private_ip_google_access: true
- project: test-project
- region: europe-west1
- role: null
- secondary_ip_range:
- - ip_cidr_range: 192.168.0.0/24
- range_name: a
- - ip_cidr_range: 192.168.1.0/24
- range_name: b
- google_compute_subnetwork.subnetwork["europe-west1/d"]:
- description: Terraform-managed.
- ip_cidr_range: 10.0.3.0/24
- log_config:
- - aggregation_interval: INTERVAL_10_MIN
- filter_expr: 'true'
- flow_sampling: 0.5
- metadata: INCLUDE_ALL_METADATA
- metadata_fields: null
- name: d
- private_ip_google_access: true
- project: test-project
- region: europe-west1
- role: null
- secondary_ip_range: []
- google_compute_subnetwork_iam_binding.binding["europe-west1/a.roles/compute.networkUser"]:
- condition: []
- members:
- - group:g-a@example.com
- - user:a@example.com
- project: test-project
- region: europe-west1
- role: roles/compute.networkUser
- subnetwork: a
- google_compute_subnetwork_iam_binding.binding["europe-west1/c.roles/compute.networkUser"]:
- condition: []
- members:
- - group:g-c@example.com
- - user:c@example.com
- project: test-project
- region: europe-west1
- role: roles/compute.networkUser
- subnetwork: c
-
-counts:
- google_compute_network: 1
- google_compute_subnetwork: 4
- google_compute_subnetwork_iam_binding: 2
-
-outputs:
- bindings: __missing__
- project_id: test-project
- subnet_ips:
- europe-west1/a: 10.0.0.0/24
- europe-west1/b: 10.0.1.0/24
- europe-west1/c: 10.0.2.0/24
- europe-west1/d: 10.0.3.0/24
- subnet_regions:
- europe-west1/a: europe-west1
- europe-west1/b: europe-west1
- europe-west1/c: europe-west1
- europe-west1/d: europe-west1
- subnet_secondary_ranges:
- europe-west1/a: {}
- europe-west1/b: {}
- europe-west1/c:
- a: 192.168.0.0/24
- b: 192.168.1.0/24
- europe-west1/d: {}
- subnet_self_links: __missing__
- subnets: __missing__
- subnets_proxy_only: {}
- subnets_psc: {}
diff --git a/tests/modules/net_vpc/test_routes.py b/tests/modules/net_vpc/test_routes.py
deleted file mode 100644
index 01d9673dd..000000000
--- a/tests/modules/net_vpc/test_routes.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# 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.
-
-import pytest
-
-_route_parameters = [('gateway', 'global/gateways/default-internet-gateway'),
- ('instance', 'zones/europe-west1-b/test'),
- ('ip', '192.168.0.128'),
- ('ilb', 'regions/europe-west1/forwardingRules/test'),
- ('vpn_tunnel', 'regions/europe-west1/vpnTunnels/foo')]
-
-
-@pytest.mark.parametrize('next_hop_type,next_hop', _route_parameters)
-def test_vpc_routes(plan_summary, next_hop_type, next_hop):
- 'Test vpc routes.'
-
- var_routes = '''{
- next-hop = {
- dest_range = "192.168.128.0/24"
- tags = null
- next_hop_type = "%s"
- next_hop = "%s"
- }
- gateway = {
- dest_range = "0.0.0.0/0",
- priority = 100
- tags = ["tag-a"]
- next_hop_type = "gateway",
- next_hop = "global/gateways/default-internet-gateway"
- }
- }''' % (next_hop_type, next_hop)
- summary = plan_summary('modules/net-vpc', tf_var_files=['common.tfvars'],
- routes=var_routes)
- assert len(summary.values) == 3
- route = summary.values[f'google_compute_route.{next_hop_type}["next-hop"]']
- assert route[f'next_hop_{next_hop_type}'] == next_hop
diff --git a/tests/modules/net_vpc/tftest.yaml b/tests/modules/net_vpc/tftest.yaml
index b2b09798b..5e9668ea4 100644
--- a/tests/modules/net_vpc/tftest.yaml
+++ b/tests/modules/net_vpc/tftest.yaml
@@ -1,4 +1,4 @@
-# Copyright 2022 Google LLC
+# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,12 +17,7 @@ common_tfvars:
- common.tfvars
tests:
- simple:
- subnets:
- peering:
shared_vpc:
- factory:
- psa_simple:
psa_routes_export:
psa_routes_import:
psa_routes_import_export:
diff --git a/tests/modules/net_vpc_firewall/auto-rules.tfvars b/tests/modules/net_vpc_firewall/auto-rules.tfvars
new file mode 100644
index 000000000..6b991da79
--- /dev/null
+++ b/tests/modules/net_vpc_firewall/auto-rules.tfvars
@@ -0,0 +1,4 @@
+default_rules_config = {
+ admin_ranges = ["10.0.0.0/8"]
+ https_ranges = []
+}
diff --git a/tests/modules/net_vpc_firewall/auto-rules.yaml b/tests/modules/net_vpc_firewall/auto-rules.yaml
new file mode 100644
index 000000000..ed3c84f23
--- /dev/null
+++ b/tests/modules/net_vpc_firewall/auto-rules.yaml
@@ -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.
+
+values:
+ google_compute_firewall.allow-admins[0]:
+ source_ranges:
+ - 10.0.0.0/8
+ google_compute_firewall.allow-tag-http[0]:
+ allow:
+ - ports:
+ - "80"
+ protocol: tcp
+ source_ranges:
+ - 130.211.0.0/22
+ - 209.85.152.0/22
+ - 209.85.204.0/22
+ - 35.191.0.0/16
+ google_compute_firewall.allow-tag-ssh[0]:
+ allow:
+ - ports:
+ - "22"
+ protocol: tcp
+ source_ranges:
+ - 35.235.240.0/20
+
+counts:
+ google_compute_firewall: 3
+ modules: 0
+ resources: 3
+
+outputs:
+ default_rules: __missing__
+ rules: {}
diff --git a/tests/modules/net_vpc_firewall/common.tfvars b/tests/modules/net_vpc_firewall/common.tfvars
new file mode 100644
index 000000000..fda6ab8f4
--- /dev/null
+++ b/tests/modules/net_vpc_firewall/common.tfvars
@@ -0,0 +1,2 @@
+project_id = "test-project"
+network = "test-network"
diff --git a/tests/modules/net_vpc_firewall/custom-rules.tfvars b/tests/modules/net_vpc_firewall/custom-rules.tfvars
new file mode 100644
index 000000000..181a8248c
--- /dev/null
+++ b/tests/modules/net_vpc_firewall/custom-rules.tfvars
@@ -0,0 +1,33 @@
+default_rules_config = {
+ disabled = true
+}
+egress_rules = {
+ allow-egress-rfc1918 = {
+ deny = false
+ description = "Allow egress to RFC 1918 ranges."
+ destination_ranges = [
+ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"
+ ]
+ }
+ allow-egress-tag = {
+ deny = false
+ description = "Allow egress from a specific tag to 0/0."
+ targets = ["target-tag"]
+ }
+ deny-egress-all = {
+ description = "Block egress."
+ }
+}
+ingress_rules = {
+ allow-ingress-ntp = {
+ description = "Allow NTP service based on tag."
+ targets = ["ntp-svc"]
+ rules = [{ protocol = "udp", ports = [123] }]
+ }
+ allow-ingress-tag = {
+ description = "Allow ingress from a specific tag."
+ source_ranges = []
+ sources = ["client-tag"]
+ targets = ["target-tag"]
+ }
+}
diff --git a/tests/modules/net_vpc_firewall/custom-rules.yaml b/tests/modules/net_vpc_firewall/custom-rules.yaml
new file mode 100644
index 000000000..652048975
--- /dev/null
+++ b/tests/modules/net_vpc_firewall/custom-rules.yaml
@@ -0,0 +1,83 @@
+# 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.
+
+values:
+ google_compute_firewall.custom-rules["allow-egress-rfc1918"]:
+ allow:
+ - ports: []
+ protocol: all
+ deny: []
+ description: Allow egress to RFC 1918 ranges.
+ destination_ranges:
+ - 10.0.0.0/8
+ - 172.16.0.0/12
+ - 192.168.0.0/16
+ direction: EGRESS
+ google_compute_firewall.custom-rules["allow-egress-tag"]:
+ allow:
+ - ports: []
+ protocol: all
+ deny: []
+ description: Allow egress from a specific tag to 0/0.
+ destination_ranges:
+ - 0.0.0.0/0
+ direction: EGRESS
+ target_tags:
+ - target-tag
+ google_compute_firewall.custom-rules["allow-ingress-ntp"]:
+ allow:
+ - ports:
+ - "123"
+ protocol: udp
+ deny: []
+ description: Allow NTP service based on tag.
+ direction: INGRESS
+ source_ranges:
+ - 0.0.0.0/0
+ source_service_accounts: null
+ source_tags: null
+ target_tags:
+ - ntp-svc
+ google_compute_firewall.custom-rules["allow-ingress-tag"]:
+ allow:
+ - ports: []
+ protocol: all
+ deny: []
+ description: Allow ingress from a specific tag.
+ direction: INGRESS
+ source_ranges: null
+ source_tags:
+ - client-tag
+ target_tags:
+ - target-tag
+ google_compute_firewall.custom-rules["deny-egress-all"]:
+ allow: []
+ deny:
+ - ports: []
+ protocol: all
+ description: Block egress.
+ direction: EGRESS
+
+counts:
+ google_compute_firewall: 5
+ modules: 0
+ resources: 5
+
+outputs:
+ default_rules:
+ admin: []
+ http: []
+ https: []
+ ssh: []
+ rules: __missing__
diff --git a/tests/modules/net_vpc_firewall/fixture/config/cidr_template.yaml b/tests/modules/net_vpc_firewall/data/cidr_template.yaml
similarity index 100%
rename from tests/modules/net_vpc_firewall/fixture/config/cidr_template.yaml
rename to tests/modules/net_vpc_firewall/data/cidr_template.yaml
diff --git a/tests/modules/net_vpc_firewall/fixture/config/firewall/load_balancers.yaml b/tests/modules/net_vpc_firewall/data/firewall/load_balancers.yaml
similarity index 100%
rename from tests/modules/net_vpc_firewall/fixture/config/firewall/load_balancers.yaml
rename to tests/modules/net_vpc_firewall/data/firewall/load_balancers.yaml
diff --git a/tests/modules/net_vpc_firewall/factory.tfvars b/tests/modules/net_vpc_firewall/factory.tfvars
new file mode 100644
index 000000000..5d2e1ab71
--- /dev/null
+++ b/tests/modules/net_vpc_firewall/factory.tfvars
@@ -0,0 +1,7 @@
+default_rules_config = {
+ disabled = true
+}
+factories_config = {
+ cidr_tpl_file = "../../tests/modules/net_vpc_firewall/data/cidr_template.yaml"
+ rules_folder = "../../tests/modules/net_vpc_firewall/data/firewall"
+}
diff --git a/tests/modules/net_vpc_firewall/factory.yaml b/tests/modules/net_vpc_firewall/factory.yaml
new file mode 100644
index 000000000..26f90bd5b
--- /dev/null
+++ b/tests/modules/net_vpc_firewall/factory.yaml
@@ -0,0 +1,54 @@
+# 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.
+
+values:
+ google_compute_firewall.custom-rules["allow-healthchecks"]:
+ allow:
+ - ports:
+ - "80"
+ - "443"
+ protocol: tcp
+ deny: []
+ description: Allow ingress from healthchecks.
+ direction: INGRESS
+ disabled: false
+ log_config: []
+ name: allow-healthchecks
+ network: test-network
+ priority: 1000
+ project: test-project
+ source_ranges:
+ - 130.211.0.0/22
+ - 209.85.152.0/22
+ - 209.85.204.0/22
+ - 35.191.0.0/16
+ source_service_accounts: null
+ source_tags: null
+ target_service_accounts: null
+ target_tags:
+ - lb-backends
+ timeouts: null
+
+counts:
+ google_compute_firewall: 1
+ modules: 0
+ resources: 1
+
+outputs:
+ default_rules:
+ admin: []
+ http: []
+ https: []
+ ssh: []
+ rules: __missing__
diff --git a/tests/modules/net_vpc_firewall/fixture/main.tf b/tests/modules/net_vpc_firewall/fixture/main.tf
deleted file mode 100644
index e69aeff10..000000000
--- a/tests/modules/net_vpc_firewall/fixture/main.tf
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * 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 "firewall" {
- source = "../../../../modules/net-vpc-firewall"
- project_id = "test-project"
- network = "test-vpc"
- default_rules_config = var.default_rules_config
- egress_rules = var.egress_rules
- ingress_rules = var.ingress_rules
- factories_config = var.factories_config
-}
diff --git a/tests/modules/net_vpc_firewall/fixture/test.rules.tfvars b/tests/modules/net_vpc_firewall/fixture/test.rules.tfvars
deleted file mode 100644
index 36944bea4..000000000
--- a/tests/modules/net_vpc_firewall/fixture/test.rules.tfvars
+++ /dev/null
@@ -1,22 +0,0 @@
-egress_rules = {
- allow-egress-rfc1918 = {
- description = "Allow egress to RFC 1918 ranges."
- is_egress = true
- destination_ranges = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
- }
- deny-egress-all = {
- description = "Block egress."
- is_deny = true
- is_egress = true
- }
-}
-ingress_rules = {
- allow-ingress-ntp = {
- description = "Allow NTP service based on tag."
- targets = ["ntp-svc"]
- rules = [{ protocol = "udp", ports = [123] }]
- }
-}
-default_rules_config = {
- disabled = true
-}
diff --git a/tests/modules/net_vpc_firewall/fixture/variables.tf b/tests/modules/net_vpc_firewall/fixture/variables.tf
deleted file mode 100644
index fd71e93b8..000000000
--- a/tests/modules/net_vpc_firewall/fixture/variables.tf
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * 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.
- */
-
-/**
- * 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 "default_rules_config" {
- type = any
- default = {}
-}
-
-variable "egress_rules" {
- type = any
- default = {}
-}
-
-variable "factories_config" {
- type = any
- default = null
-}
-
-variable "ingress_rules" {
- type = any
- default = {}
-}
diff --git a/tests/modules/net_vpc_firewall/test_plan.py b/tests/modules/net_vpc_firewall/test_plan.py_
similarity index 100%
rename from tests/modules/net_vpc_firewall/test_plan.py
rename to tests/modules/net_vpc_firewall/test_plan.py_
diff --git a/tests/blueprints/factories/bigquery_factory/fixture/tables/table_a.yaml b/tests/modules/net_vpc_firewall/tftest.yaml
similarity index 83%
rename from tests/blueprints/factories/bigquery_factory/fixture/tables/table_a.yaml
rename to tests/modules/net_vpc_firewall/tftest.yaml
index 05adbcb02..e11810c45 100644
--- a/tests/blueprints/factories/bigquery_factory/fixture/tables/table_a.yaml
+++ b/tests/modules/net_vpc_firewall/tftest.yaml
@@ -12,6 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-dataset: dataset_a
-table: table_a
-schema: [{name: "test", type: "STRING"},{name: "test2", type: "INT64"}]
+module: modules/net-vpc-firewall
+common_tfvars:
+ - common.tfvars
+tests:
+ auto-rules:
+ custom-rules:
+ factory:
diff --git a/tests/modules/organization/examples/basic.yaml b/tests/modules/organization/examples/basic.yaml
new file mode 100644
index 000000000..f7b63a1d4
--- /dev/null
+++ b/tests/modules/organization/examples/basic.yaml
@@ -0,0 +1,146 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.org.google_org_policy_policy.default["compute.disableGuestAttributesAccess"]:
+ name: organizations/1234567890/policies/compute.disableGuestAttributesAccess
+ parent: organizations/1234567890
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ module.org.google_org_policy_policy.default["constraints/compute.skipDefaultNetworkCreation"]:
+ name: organizations/1234567890/policies/constraints/compute.skipDefaultNetworkCreation
+ parent: organizations/1234567890
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ module.org.google_org_policy_policy.default["constraints/compute.trustedImageProjects"]:
+ name: organizations/1234567890/policies/constraints/compute.trustedImageProjects
+ parent: organizations/1234567890
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: null
+ values:
+ - allowed_values:
+ - projects/my-project
+ denied_values: null
+ module.org.google_org_policy_policy.default["constraints/compute.vmExternalIpAccess"]:
+ name: organizations/1234567890/policies/constraints/compute.vmExternalIpAccess
+ parent: organizations/1234567890
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: 'TRUE'
+ enforce: null
+ values: []
+ module.org.google_org_policy_policy.default["constraints/iam.allowedPolicyMemberDomains"]:
+ name: organizations/1234567890/policies/constraints/iam.allowedPolicyMemberDomains
+ parent: organizations/1234567890
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: null
+ values:
+ - allowed_values:
+ - C0xxxxxxx
+ - C0yyyyyyy
+ denied_values: null
+ module.org.google_org_policy_policy.default["iam.disableServiceAccountKeyCreation"]:
+ name: organizations/1234567890/policies/iam.disableServiceAccountKeyCreation
+ parent: organizations/1234567890
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ module.org.google_org_policy_policy.default["iam.disableServiceAccountKeyUpload"]:
+ name: organizations/1234567890/policies/iam.disableServiceAccountKeyUpload
+ parent: organizations/1234567890
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition:
+ - description: test condition
+ expression: resource.matchTagId("tagKeys/1234", "tagValues/1234")
+ location: somewhere
+ title: condition
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'FALSE'
+ values: []
+ module.org.google_organization_iam_binding.authoritative["roles/owner"]:
+ condition: []
+ members:
+ - group:cloud-owners@example.org
+ org_id: '1234567890'
+ role: roles/owner
+ module.org.google_organization_iam_binding.authoritative["roles/projectCreator"]:
+ condition: []
+ members:
+ - group:cloud-owners@example.org
+ org_id: '1234567890'
+ role: roles/projectCreator
+ module.org.google_organization_iam_binding.authoritative["roles/resourcemanager.projectCreator"]:
+ condition: []
+ members:
+ - group:cloud-admins@example.org
+ org_id: '1234567890'
+ role: roles/resourcemanager.projectCreator
+ module.org.google_organization_iam_member.additive["roles/compute.admin-user:compute@example.org"]:
+ condition: []
+ member: user:compute@example.org
+ org_id: '1234567890'
+ role: roles/compute.admin
+ module.org.google_organization_iam_member.additive["roles/container.viewer-user:compute@example.org"]:
+ condition: []
+ member: user:compute@example.org
+ org_id: '1234567890'
+ role: roles/container.viewer
+counts:
+ google_org_policy_policy: 8
+ google_organization_iam_binding: 3
diff --git a/tests/modules/organization/examples/custom-constraints.yaml b/tests/modules/organization/examples/custom-constraints.yaml
new file mode 100644
index 000000000..db3023985
--- /dev/null
+++ b/tests/modules/organization/examples/custom-constraints.yaml
@@ -0,0 +1,39 @@
+# 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.
+
+values:
+ module.org.google_org_policy_custom_constraint.constraint["custom.gkeEnableAutoUpgrade"]:
+ action_type: ALLOW
+ condition: resource.management.autoUpgrade == true
+ description: All node pools must have node auto-upgrade enabled.
+ display_name: Enable node auto-upgrade
+ method_types:
+ - CREATE
+ name: custom.gkeEnableAutoUpgrade
+ parent: organizations/1122334455
+ resource_types:
+ - container.googleapis.com/NodePool
+
+ module.org.google_org_policy_policy.default["custom.gkeEnableAutoUpgrade"]:
+ name: organizations/1122334455/policies/custom.gkeEnableAutoUpgrade
+ parent: organizations/1122334455
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
diff --git a/tests/modules/organization/firewall_policies_factory.yaml b/tests/modules/organization/examples/hfw.yaml
similarity index 67%
rename from tests/modules/organization/firewall_policies_factory.yaml
rename to tests/modules/organization/examples/hfw.yaml
index 85e565fdd..91ced6db4 100644
--- a/tests/modules/organization/firewall_policies_factory.yaml
+++ b/tests/modules/organization/examples/hfw.yaml
@@ -13,17 +13,18 @@
# limitations under the License.
values:
- google_compute_firewall_policy.policy["factory-1"]:
- description: null
- parent: organizations/1234567890
- short_name: factory-1
- timeouts: null
- google_compute_firewall_policy_rule.rule["factory-1-allow-admins"]:
+ module.org.google_compute_firewall_policy.policy["iap-policy"]:
+ parent: organizations/1122334455
+ short_name: iap-policy
+ module.org.google_compute_firewall_policy_association.association["iap_policy"]:
+ attachment_target: organizations/1122334455
+ name: organizations-1122334455
+ module.org.google_compute_firewall_policy_rule.rule["iap-policy-allow-admins"]:
action: allow
description: Access from the admin subnet to all subnets
direction: INGRESS
disabled: null
- enable_logging: null
+ enable_logging: false
match:
- dest_ip_ranges: null
layer4_configs:
@@ -31,18 +32,17 @@ values:
ports: []
src_ip_ranges:
- 10.0.0.0/8
- - 172.168.0.0/12
+ - 172.16.0.0/12
- 192.168.0.0/16
priority: 1000
target_resources: null
target_service_accounts: null
- timeouts: null
- google_compute_firewall_policy_rule.rule["factory-1-allow-ssh-from-iap"]:
+ module.org.google_compute_firewall_policy_rule.rule["iap-policy-allow-iap-ssh"]:
action: allow
- description: Enable SSH from IAP
+ description: Always allow ssh from IAP.
direction: INGRESS
disabled: null
- enable_logging: null
+ enable_logging: false
match:
- dest_ip_ranges: null
layer4_configs:
@@ -51,11 +51,12 @@ values:
- '22'
src_ip_ranges:
- 35.235.240.0/20
- priority: 1002
+ priority: 100
target_resources: null
target_service_accounts: null
timeouts: null
counts:
google_compute_firewall_policy: 1
+ google_compute_firewall_policy_association: 1
google_compute_firewall_policy_rule: 2
diff --git a/tests/modules/organization/examples/logging.yaml b/tests/modules/organization/examples/logging.yaml
new file mode 100644
index 000000000..68df72bc5
--- /dev/null
+++ b/tests/modules/organization/examples/logging.yaml
@@ -0,0 +1,70 @@
+# 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.
+
+values:
+ module.org.google_bigquery_dataset_iam_member.bq-sinks-binding["info"]:
+ condition: []
+ role: roles/bigquery.dataEditor
+ module.org.google_logging_organization_exclusion.logging-exclusion["no-gce-instances"]:
+ disabled: null
+ filter: resource.type=gce_instance
+ name: no-gce-instances
+ org_id: '1122334455'
+ module.org.google_logging_organization_sink.sink["debug"]:
+ disabled: false
+ exclusions:
+ - description: null
+ disabled: false
+ filter: logName:compute
+ name: no-compute
+ filter: severity=DEBUG
+ include_children: true
+ name: debug
+ org_id: '1122334455'
+ module.org.google_logging_organization_sink.sink["info"]:
+ disabled: false
+ exclusions: []
+ filter: severity=INFO
+ include_children: true
+ name: info
+ org_id: '1122334455'
+ module.org.google_logging_organization_sink.sink["notice"]:
+ disabled: false
+ exclusions: []
+ filter: severity=NOTICE
+ include_children: true
+ name: notice
+ org_id: '1122334455'
+ module.org.google_logging_organization_sink.sink["warnings"]:
+ destination: storage.googleapis.com/gcs_sink
+ disabled: false
+ exclusions: []
+ filter: severity=WARNING
+ include_children: true
+ name: warnings
+ org_id: '1122334455'
+ module.pubsub.google_pubsub_topic.default:
+ kms_key_name: null
+ labels: null
+ message_retention_duration: null
+ name: pubsub_sink
+ project: project-id
+
+counts:
+ google_bigquery_dataset_iam_member: 1
+ google_logging_organization_exclusion: 1
+ google_logging_organization_sink: 4
+ google_project_iam_member: 1
+ google_pubsub_topic_iam_member: 1
+ google_storage_bucket_iam_member: 1
diff --git a/tests/modules/organization/examples/network-tags.yaml b/tests/modules/organization/examples/network-tags.yaml
new file mode 100644
index 000000000..9cacffb61
--- /dev/null
+++ b/tests/modules/organization/examples/network-tags.yaml
@@ -0,0 +1,47 @@
+# 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.
+
+values:
+ module.org.google_tags_tag_key.default["net-environment"]:
+ description: This is a network tag.
+ parent: organizations/1122334455
+ purpose: GCE_FIREWALL
+ purpose_data:
+ network: my_project/my_vpc
+ short_name: net-environment
+ timeouts: null
+ module.org.google_tags_tag_key_iam_binding.default["net-environment:roles/resourcemanager.tagAdmin"]:
+ condition: []
+ members:
+ - group:admins@example.com
+ role: roles/resourcemanager.tagAdmin
+ module.org.google_tags_tag_value.default["net-environment/dev"]:
+ description: Managed by the Terraform organization module.
+ short_name: dev
+ timeouts: null
+ module.org.google_tags_tag_value.default["net-environment/prod"]:
+ description: 'Environment: production.'
+ short_name: prod
+ timeouts: null
+ module.org.google_tags_tag_value_iam_binding.default["net-environment/prod:roles/resourcemanager.tagUser"]:
+ condition: []
+ members:
+ - user:user1@example.com
+ role: roles/resourcemanager.tagUser
+
+counts:
+ google_tags_tag_key: 1
+ google_tags_tag_key_iam_binding: 1
+ google_tags_tag_value: 2
+ google_tags_tag_value_iam_binding: 1
diff --git a/tests/modules/organization/examples/roles.yaml b/tests/modules/organization/examples/roles.yaml
new file mode 100644
index 000000000..4705d1958
--- /dev/null
+++ b/tests/modules/organization/examples/roles.yaml
@@ -0,0 +1,33 @@
+# 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.
+
+values:
+ module.org.google_organization_iam_binding.authoritative["organizations/1122334455/roles/myRole"]:
+ condition: []
+ members:
+ - user:me@example.com
+ org_id: '1122334455'
+ role: organizations/1122334455/roles/myRole
+ module.org.google_organization_iam_custom_role.roles["myRole"]:
+ description: Terraform-managed.
+ org_id: '1122334455'
+ permissions:
+ - compute.instances.list
+ role_id: myRole
+ stage: GA
+ title: Custom role myRole
+
+counts:
+ google_organization_iam_binding: 1
+ google_organization_iam_custom_role: 1
diff --git a/tests/modules/organization/examples/tags.yaml b/tests/modules/organization/examples/tags.yaml
new file mode 100644
index 000000000..afbb7f8ff
--- /dev/null
+++ b/tests/modules/organization/examples/tags.yaml
@@ -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.
+
+values:
+ module.org.google_tags_tag_binding.binding["env-prod"]:
+ parent: //cloudresourcemanager.googleapis.com/organizations/1122334455
+ timeouts: null
+ module.org.google_tags_tag_binding.binding["foo"]:
+ parent: //cloudresourcemanager.googleapis.com/organizations/1122334455
+ tag_value: tagValues/12345678
+ timeouts: null
+ module.org.google_tags_tag_key.default["environment"]:
+ description: Environment specification.
+ parent: organizations/1122334455
+ purpose: null
+ purpose_data: null
+ short_name: environment
+ timeouts: null
+ module.org.google_tags_tag_key_iam_binding.default["environment:roles/resourcemanager.tagAdmin"]:
+ condition: []
+ members:
+ - group:admins@example.com
+ role: roles/resourcemanager.tagAdmin
+ module.org.google_tags_tag_value.default["environment/dev"]:
+ description: Managed by the Terraform organization module.
+ short_name: dev
+ timeouts: null
+ module.org.google_tags_tag_value.default["environment/prod"]:
+ description: 'Environment: production.'
+ short_name: prod
+ timeouts: null
+ module.org.google_tags_tag_value_iam_binding.default["environment/prod:roles/resourcemanager.tagViewer"]:
+ condition: []
+ members:
+ - user:user1@example.com
+ role: roles/resourcemanager.tagViewer
+
+counts:
+ google_tags_tag_binding: 2
+ google_tags_tag_key: 1
+ google_tags_tag_key_iam_binding: 1
+ google_tags_tag_value: 2
diff --git a/tests/modules/organization/firewall_policies.tfvars b/tests/modules/organization/firewall_policies.tfvars
deleted file mode 100644
index 603cd3a47..000000000
--- a/tests/modules/organization/firewall_policies.tfvars
+++ /dev/null
@@ -1,45 +0,0 @@
-firewall_policies = {
- policy1 = {
- allow-ingress = {
- description = ""
- direction = "INGRESS"
- action = "allow"
- priority = 100
- ranges = ["10.0.0.0/8"]
- ports = {
- tcp = ["22"]
- }
- target_service_accounts = null
- target_resources = null
- logging = false
- }
- deny-egress = {
- description = ""
- direction = "EGRESS"
- action = "deny"
- priority = 200
- ranges = ["192.168.0.0/24"]
- ports = {
- tcp = ["443"]
- }
- target_service_accounts = null
- target_resources = null
- logging = false
- }
- }
- policy2 = {
- allow-ingress = {
- description = ""
- direction = "INGRESS"
- action = "allow"
- priority = 100
- ranges = ["10.0.0.0/8"]
- ports = {
- tcp = ["22"]
- }
- target_service_accounts = null
- target_resources = null
- logging = false
- }
- }
-}
diff --git a/tests/modules/organization/firewall_policies_factory.tfvars b/tests/modules/organization/firewall_policies_factory.tfvars
deleted file mode 100644
index 3e1cf1813..000000000
--- a/tests/modules/organization/firewall_policies_factory.tfvars
+++ /dev/null
@@ -1,5 +0,0 @@
-firewall_policy_factory = {
- cidr_file = "../../tests/modules/organization/data/firewall-cidrs.yaml"
- policy_name = "factory-1"
- rules_file = "../../tests/modules/organization/data/firewall-rules.yaml"
-}
diff --git a/tests/modules/organization/firewall_policies_factory_combined.tfvars b/tests/modules/organization/firewall_policies_factory_combined.tfvars
index 6f848aa99..7ea51bb0c 100644
--- a/tests/modules/organization/firewall_policies_factory_combined.tfvars
+++ b/tests/modules/organization/firewall_policies_factory_combined.tfvars
@@ -1 +1,51 @@
-# skip boilerplate check
+firewall_policies = {
+ policy1 = {
+ allow-ingress = {
+ description = ""
+ direction = "INGRESS"
+ action = "allow"
+ priority = 100
+ ranges = ["10.0.0.0/8"]
+ ports = {
+ tcp = ["22"]
+ }
+ target_service_accounts = null
+ target_resources = null
+ logging = false
+ }
+ deny-egress = {
+ description = ""
+ direction = "EGRESS"
+ action = "deny"
+ priority = 200
+ ranges = ["192.168.0.0/24"]
+ ports = {
+ tcp = ["443"]
+ }
+ target_service_accounts = null
+ target_resources = null
+ logging = false
+ }
+ }
+ policy2 = {
+ allow-ingress = {
+ description = ""
+ direction = "INGRESS"
+ action = "allow"
+ priority = 100
+ ranges = ["10.0.0.0/8"]
+ ports = {
+ tcp = ["22"]
+ }
+ target_service_accounts = null
+ target_resources = null
+ logging = false
+ }
+ }
+}
+
+firewall_policy_factory = {
+ cidr_file = "../../tests/modules/organization/data/firewall-cidrs.yaml"
+ policy_name = "factory-1"
+ rules_file = "../../tests/modules/organization/data/firewall-rules.yaml"
+}
diff --git a/tests/modules/organization/fixture/main.tf b/tests/modules/organization/fixture/main.tf
deleted file mode 100644
index feb49f11e..000000000
--- a/tests/modules/organization/fixture/main.tf
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * 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 "test" {
- source = "../../../../modules/organization"
- organization_id = "organizations/1234567890"
- custom_roles = var.custom_roles
- firewall_policies = var.firewall_policies
- firewall_policy_association = var.firewall_policy_association
- firewall_policy_factory = var.firewall_policy_factory
- group_iam = var.group_iam
- iam = var.iam
- iam_additive = var.iam_additive
- iam_additive_members = var.iam_additive_members
- iam_audit_config = var.iam_audit_config
- logging_sinks = var.logging_sinks
- logging_exclusions = var.logging_exclusions
- network_tags = var.network_tags
- org_policies = var.org_policies
- org_policies_data_path = var.org_policies_data_path
- org_policy_custom_constraints = var.org_policy_custom_constraints
- org_policy_custom_constraints_data_path = var.org_policy_custom_constraints_data_path
- tag_bindings = var.tag_bindings
- tags = var.tags
-}
diff --git a/tests/modules/organization/fixture/test.logging-sinks.tfvars b/tests/modules/organization/fixture/test.logging-sinks.tfvars
deleted file mode 100644
index 95a272e1f..000000000
--- a/tests/modules/organization/fixture/test.logging-sinks.tfvars
+++ /dev/null
@@ -1,29 +0,0 @@
-logging_sinks = {
- warning = {
- destination = "mybucket"
- type = "storage"
- filter = "severity=WARNING"
- }
- info = {
- destination = "projects/myproject/datasets/mydataset"
- type = "bigquery"
- filter = "severity=INFO"
- disabled = true
- }
- notice = {
- destination = "projects/myproject/topics/mytopic"
- type = "pubsub"
- filter = "severity=NOTICE"
- include_children = false
- }
- debug = {
- destination = "projects/myproject/locations/global/buckets/mybucket"
- type = "logging"
- filter = "severity=DEBUG"
- include_children = false
- exclusions = {
- no-compute = "logName:compute"
- no-container = "logName:container"
- }
- }
-}
diff --git a/tests/modules/organization/fixture/variables.tf b/tests/modules/organization/fixture/variables.tf
deleted file mode 100644
index 35c777c4e..000000000
--- a/tests/modules/organization/fixture/variables.tf
+++ /dev/null
@@ -1,105 +0,0 @@
-/**
- * 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 "custom_roles" {
- type = any
- default = {}
-}
-
-variable "group_iam" {
- type = any
- default = {}
-}
-
-variable "iam" {
- type = any
- default = {}
-}
-
-variable "iam_additive" {
- type = any
- default = {}
-}
-
-variable "iam_additive_members" {
- type = any
- default = {}
-}
-
-variable "iam_audit_config" {
- type = any
- default = {}
-}
-
-variable "firewall_policies" {
- type = any
- default = {}
-}
-
-variable "firewall_policy_association" {
- type = any
- default = {}
-}
-
-variable "firewall_policy_factory" {
- type = any
- default = null
-}
-
-variable "logging_sinks" {
- type = any
- default = {}
-}
-
-variable "logging_exclusions" {
- type = map(string)
- default = {}
-}
-
-variable "network_tags" {
- type = any
- default = null
-}
-
-variable "org_policies" {
- type = any
- default = {}
-}
-
-variable "org_policies_data_path" {
- type = any
- default = null
-}
-
-variable "org_policy_custom_constraints" {
- type = any
- default = {}
-}
-
-variable "org_policy_custom_constraints_data_path" {
- type = any
- default = null
-}
-
-variable "tag_bindings" {
- type = any
- default = null
-}
-
-variable "tags" {
- type = any
- default = null
-}
diff --git a/tests/modules/organization/iam.tfvars b/tests/modules/organization/iam.tfvars
deleted file mode 100644
index 699631277..000000000
--- a/tests/modules/organization/iam.tfvars
+++ /dev/null
@@ -1,18 +0,0 @@
-group_iam = {
- "owners@example.org" = [
- "roles/owner",
- "roles/resourcemanager.folderAdmin"
- ],
- "viewers@example.org" = [
- "roles/viewer"
- ]
-}
-iam = {
- "roles/owner" = [
- "user:one@example.org",
- "user:two@example.org"
- ],
- "roles/browser" = [
- "domain:example.org"
- ]
-}
diff --git a/tests/modules/organization/iam.yaml b/tests/modules/organization/iam.yaml
deleted file mode 100644
index 7b1a8cb95..000000000
--- a/tests/modules/organization/iam.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-# 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.
-
-values:
- google_organization_iam_binding.authoritative["roles/browser"]:
- condition: []
- members:
- - domain:example.org
- org_id: '1234567890'
- role: roles/browser
- google_organization_iam_binding.authoritative["roles/owner"]:
- condition: []
- members:
- - group:owners@example.org
- - user:one@example.org
- - user:two@example.org
- org_id: '1234567890'
- role: roles/owner
- google_organization_iam_binding.authoritative["roles/resourcemanager.folderAdmin"]:
- condition: []
- members:
- - group:owners@example.org
- org_id: '1234567890'
- role: roles/resourcemanager.folderAdmin
- google_organization_iam_binding.authoritative["roles/viewer"]:
- condition: []
- members:
- - group:viewers@example.org
- org_id: '1234567890'
- role: roles/viewer
-
-counts:
- google_organization_iam_binding: 4
diff --git a/tests/modules/organization/iam_additive.tfvars b/tests/modules/organization/iam_additive.tfvars
deleted file mode 100644
index 823d70ad3..000000000
--- a/tests/modules/organization/iam_additive.tfvars
+++ /dev/null
@@ -1,4 +0,0 @@
-iam = {
- "user:one@example.org" = ["roles/owner"],
- "user:two@example.org" = ["roles/owner", "roles/editor"]
-}
diff --git a/tests/modules/organization/logging.tfvars b/tests/modules/organization/logging.tfvars
deleted file mode 100644
index 95a272e1f..000000000
--- a/tests/modules/organization/logging.tfvars
+++ /dev/null
@@ -1,29 +0,0 @@
-logging_sinks = {
- warning = {
- destination = "mybucket"
- type = "storage"
- filter = "severity=WARNING"
- }
- info = {
- destination = "projects/myproject/datasets/mydataset"
- type = "bigquery"
- filter = "severity=INFO"
- disabled = true
- }
- notice = {
- destination = "projects/myproject/topics/mytopic"
- type = "pubsub"
- filter = "severity=NOTICE"
- include_children = false
- }
- debug = {
- destination = "projects/myproject/locations/global/buckets/mybucket"
- type = "logging"
- filter = "severity=DEBUG"
- include_children = false
- exclusions = {
- no-compute = "logName:compute"
- no-container = "logName:container"
- }
- }
-}
diff --git a/tests/modules/organization/logging.yaml b/tests/modules/organization/logging.yaml
deleted file mode 100644
index 8038c9ab5..000000000
--- a/tests/modules/organization/logging.yaml
+++ /dev/null
@@ -1,86 +0,0 @@
-# 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.
-
-values:
- google_bigquery_dataset_iam_member.bq-sinks-binding["info"]:
- condition: []
- dataset_id: mydataset
- project: myproject
- role: roles/bigquery.dataEditor
- google_logging_organization_sink.sink["debug"]:
- description: debug (Terraform-managed).
- destination: logging.googleapis.com/projects/myproject/locations/global/buckets/mybucket
- disabled: false
- exclusions:
- - description: null
- disabled: false
- filter: logName:compute
- name: no-compute
- - description: null
- disabled: false
- filter: logName:container
- name: no-container
- filter: severity=DEBUG
- include_children: false
- name: debug
- org_id: '1234567890'
- google_logging_organization_sink.sink["info"]:
- description: info (Terraform-managed).
- destination: bigquery.googleapis.com/projects/myproject/datasets/mydataset
- disabled: true
- exclusions: []
- filter: severity=INFO
- include_children: true
- name: info
- org_id: '1234567890'
- google_logging_organization_sink.sink["notice"]:
- description: notice (Terraform-managed).
- destination: pubsub.googleapis.com/projects/myproject/topics/mytopic
- disabled: false
- exclusions: []
- filter: severity=NOTICE
- include_children: false
- name: notice
- org_id: '1234567890'
- google_logging_organization_sink.sink["warning"]:
- description: warning (Terraform-managed).
- destination: storage.googleapis.com/mybucket
- disabled: false
- exclusions: []
- filter: severity=WARNING
- include_children: true
- name: warning
- org_id: '1234567890'
- google_project_iam_member.bucket-sinks-binding["debug"]:
- condition:
- - expression: resource.name.endsWith('projects/myproject/locations/global/buckets/mybucket')
- title: debug bucket writer
- project: myproject
- role: roles/logging.bucketWriter
- google_pubsub_topic_iam_member.pubsub-sinks-binding["notice"]:
- condition: []
- project: myproject
- role: roles/pubsub.publisher
- topic: mytopic
- google_storage_bucket_iam_member.storage-sinks-binding["warning"]:
- bucket: mybucket
- condition: []
- role: roles/storage.objectCreator
-
-counts:
- google_bigquery_dataset_iam_member: 1
- google_logging_organization_sink: 4
- google_project_iam_member: 1
- google_pubsub_topic_iam_member: 1
- google_storage_bucket_iam_member: 1
diff --git a/tests/modules/organization/logging_exclusions.tfvars b/tests/modules/organization/logging_exclusions.tfvars
deleted file mode 100644
index 75c35604e..000000000
--- a/tests/modules/organization/logging_exclusions.tfvars
+++ /dev/null
@@ -1,4 +0,0 @@
-logging_exclusions = {
- exclusion1 = "resource.type=gce_instance"
- exclusion2 = "severity=NOTICE"
-}
diff --git a/tests/modules/organization/org_policies_boolean.yaml b/tests/modules/organization/org_policies_boolean.yaml
index 310997a4c..00f98b06c 100644
--- a/tests/modules/organization/org_policies_boolean.yaml
+++ b/tests/modules/organization/org_policies_boolean.yaml
@@ -33,11 +33,6 @@ values:
- inherit_from_parent: null
reset: null
rules:
- - allow_all: null
- condition: []
- deny_all: null
- enforce: 'FALSE'
- values: []
- allow_all: null
condition:
- description: test condition
@@ -47,6 +42,11 @@ values:
deny_all: null
enforce: 'TRUE'
values: []
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'FALSE'
+ values: []
timeouts: null
counts:
diff --git a/tests/modules/organization/org_policies_list.yaml b/tests/modules/organization/org_policies_list.yaml
index 39c3a3896..393eadde4 100644
--- a/tests/modules/organization/org_policies_list.yaml
+++ b/tests/modules/organization/org_policies_list.yaml
@@ -20,14 +20,6 @@ values:
- inherit_from_parent: null
reset: null
rules:
- - allow_all: null
- condition: []
- deny_all: null
- enforce: null
- values:
- - allowed_values: null
- denied_values:
- - in:EXTERNAL
- allow_all: null
condition:
- description: test condition
@@ -49,6 +41,14 @@ values:
deny_all: null
enforce: null
values: []
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: null
+ values:
+ - allowed_values: null
+ denied_values:
+ - in:EXTERNAL
timeouts: null
google_org_policy_policy.default["compute.vmExternalIpAccess"]:
name: organizations/1234567890/policies/compute.vmExternalIpAccess
diff --git a/tests/modules/organization/tags.tfvars b/tests/modules/organization/tags.tfvars
index 31c6764e3..2a4dcb42f 100644
--- a/tests/modules/organization/tags.tfvars
+++ b/tests/modules/organization/tags.tfvars
@@ -10,6 +10,13 @@ tags = {
iam = null
values = null
}
+ baz = {
+ id = "tagKeys/1234567890"
+ values = {
+ one = null
+ two = null
+ }
+ }
foobar = {
description = "Foobar tag."
iam = {
@@ -38,6 +45,15 @@ tags = {
]
}
}
+ four = {
+ description = "Foobar 4."
+ id = "tagValues/1234567890"
+ iam = {
+ "roles/resourcemanager.tagViewer" = [
+ "user:user4@example.com"
+ ]
+ }
+ }
}
}
}
diff --git a/tests/modules/organization/tags.yaml b/tests/modules/organization/tags.yaml
index 7da6f77bb..3e5524d47 100644
--- a/tests/modules/organization/tags.yaml
+++ b/tests/modules/organization/tags.yaml
@@ -38,11 +38,11 @@ values:
purpose_data:
network: foobar
short_name: net_environment
- google_tags_tag_key_iam_binding.default["foobar:roles/resourcemanager.tagAdmin"]:
- condition: []
+ ? google_tags_tag_key_iam_binding.default["foobar:roles/resourcemanager.tagAdmin"]
+ : condition: []
members:
- - user:user1@example.com
- - user:user2@example.com
+ - user:user1@example.com
+ - user:user2@example.com
role: roles/resourcemanager.tagAdmin
google_tags_tag_value.default["foobar/one"]:
description: Managed by the Terraform organization module.
@@ -53,24 +53,24 @@ values:
google_tags_tag_value.default["foobar/two"]:
description: Foobar 2.
short_name: two
- google_tags_tag_value_iam_binding.default["foobar/three:roles/resourcemanager.tagAdmin"]:
- condition: []
+ ? google_tags_tag_value_iam_binding.default["foobar/three:roles/resourcemanager.tagAdmin"]
+ : condition: []
members:
- - user:user4@example.com
+ - user:user4@example.com
role: roles/resourcemanager.tagAdmin
- google_tags_tag_value_iam_binding.default["foobar/three:roles/resourcemanager.tagViewer"]:
- condition: []
+ ? google_tags_tag_value_iam_binding.default["foobar/three:roles/resourcemanager.tagViewer"]
+ : condition: []
members:
- - user:user3@example.com
+ - user:user3@example.com
role: roles/resourcemanager.tagViewer
- google_tags_tag_value_iam_binding.default["foobar/two:roles/resourcemanager.tagViewer"]:
- condition: []
+ ? google_tags_tag_value_iam_binding.default["foobar/two:roles/resourcemanager.tagViewer"]
+ : condition: []
members:
- - user:user3@example.com
+ - user:user3@example.com
role: roles/resourcemanager.tagViewer
counts:
google_tags_tag_key: 4
google_tags_tag_key_iam_binding: 1
- google_tags_tag_value: 3
- google_tags_tag_value_iam_binding: 3
+ google_tags_tag_value: 5
+ google_tags_tag_value_iam_binding: 4
diff --git a/tests/modules/organization/test_plan_org_policies.py b/tests/modules/organization/test_plan_org_policies.py
index 1e041dbc4..f5002523a 100644
--- a/tests/modules/organization/test_plan_org_policies.py
+++ b/tests/modules/organization/test_plan_org_policies.py
@@ -12,8 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import pathlib
-
import pytest
_params = ['boolean', 'list']
diff --git a/tests/modules/organization/tftest.yaml b/tests/modules/organization/tftest.yaml
index c88e05c12..7568b732a 100644
--- a/tests/modules/organization/tftest.yaml
+++ b/tests/modules/organization/tftest.yaml
@@ -1,4 +1,4 @@
-# Copyright 2022 Google LLC
+# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,17 +19,8 @@ common_tfvars:
tests:
audit_config:
- iam:
- iam_additive:
- logging:
- logging_exclusions:
org_policies_list:
org_policies_boolean:
org_policies_custom_constraints:
- tags:
- firewall_policies:
- firewall_policies_factory:
firewall_policies_factory_combined:
- tfvars:
- - firewall_policies.tfvars
- - firewall_policies_factory.tfvars
+ tags:
diff --git a/tests/modules/project/common.tfvars b/tests/modules/project/common.tfvars
new file mode 100644
index 000000000..9bd31b6fc
--- /dev/null
+++ b/tests/modules/project/common.tfvars
@@ -0,0 +1 @@
+name = "my-project"
diff --git a/tests/modules/project/examples/basic.yaml b/tests/modules/project/examples/basic.yaml
new file mode 100644
index 000000000..a6ae5af3e
--- /dev/null
+++ b/tests/modules/project/examples/basic.yaml
@@ -0,0 +1,39 @@
+# 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.
+
+
+values:
+ module.project.google_project.project[0]:
+ auto_create_network: false
+ billing_account: 123456-123456-123456
+ folder_id: '1234567890'
+ labels: null
+ name: foo-myproject
+ org_id: null
+ project_id: foo-myproject
+ skip_delete: false
+ module.project.google_project_service.project_services["container.googleapis.com"]:
+ disable_dependent_services: false
+ disable_on_destroy: false
+ project: foo-myproject
+ service: container.googleapis.com
+ module.project.google_project_service.project_services["stackdriver.googleapis.com"]:
+ disable_dependent_services: false
+ disable_on_destroy: false
+ project: foo-myproject
+ service: stackdriver.googleapis.com
+
+counts:
+ google_project: 1
+ google_project_service: 2
diff --git a/tests/modules/project/examples/iam-additive-members.yaml b/tests/modules/project/examples/iam-additive-members.yaml
new file mode 100644
index 000000000..6a517a4a1
--- /dev/null
+++ b/tests/modules/project/examples/iam-additive-members.yaml
@@ -0,0 +1,33 @@
+# 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.
+
+values:
+ module.project.google_project.project[0]:
+ project_id: project-example
+ module.project.google_project_iam_member.additive["roles/editor-user:two@example.org"]:
+ condition: []
+ project: project-example
+ role: roles/editor
+ module.project.google_project_iam_member.additive["roles/owner-user:one@example.org"]:
+ condition: []
+ project: project-example
+ role: roles/owner
+ module.project.google_project_iam_member.additive["roles/owner-user:two@example.org"]:
+ condition: []
+ project: project-example
+ role: roles/owner
+
+counts:
+ google_project: 1
+ google_project_iam_member: 3
diff --git a/tests/modules/project/examples/iam-additive.yaml b/tests/modules/project/examples/iam-additive.yaml
new file mode 100644
index 000000000..5bab82232
--- /dev/null
+++ b/tests/modules/project/examples/iam-additive.yaml
@@ -0,0 +1,36 @@
+# 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.
+
+values:
+ module.project.google_project.project[0]: {}
+ module.project.google_project_iam_member.additive["roles/owner-group:three@example.org"]:
+ condition: []
+ project: project-example
+ role: roles/owner
+ module.project.google_project_iam_member.additive["roles/storage.objectAdmin-group:two@example.org"]:
+ condition: []
+ project: project-example
+ role: roles/storage.objectAdmin
+ module.project.google_project_iam_member.additive["roles/viewer-group:one@example.org"]:
+ condition: []
+ project: project-example
+ role: roles/viewer
+ module.project.google_project_iam_member.additive["roles/viewer-group:two@xample.org"]:
+ condition: []
+ project: project-example
+ role: roles/viewer
+
+counts:
+ google_project: 1
+ google_project_iam_member: 4
diff --git a/tests/modules/project/examples/iam-authoritative.yaml b/tests/modules/project/examples/iam-authoritative.yaml
new file mode 100644
index 000000000..f190a4298
--- /dev/null
+++ b/tests/modules/project/examples/iam-authoritative.yaml
@@ -0,0 +1,39 @@
+# 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.
+
+values:
+ module.project.google_project.project[0]: {}
+ module.project.google_project_iam_binding.authoritative["roles/container.hostServiceAgentUser"]:
+ condition: []
+ members:
+ - serviceAccount:my_gke_service_account
+ project: foo-project-example
+ role: roles/container.hostServiceAgentUser
+ module.project.google_project_service.project_services["container.googleapis.com"]:
+ disable_dependent_services: false
+ disable_on_destroy: false
+ project: foo-project-example
+ service: container.googleapis.com
+ timeouts: null
+ module.project.google_project_service.project_services["stackdriver.googleapis.com"]:
+ disable_dependent_services: false
+ disable_on_destroy: false
+ project: foo-project-example
+ service: stackdriver.googleapis.com
+ timeouts: null
+
+counts:
+ google_project: 1
+ google_project_iam_binding: 1
+ google_project_service: 2
diff --git a/tests/modules/project/examples/iam-group.yaml b/tests/modules/project/examples/iam-group.yaml
new file mode 100644
index 000000000..02728d019
--- /dev/null
+++ b/tests/modules/project/examples/iam-group.yaml
@@ -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.
+
+values:
+ module.project.google_project.project[0]: {}
+ module.project.google_project_iam_binding.authoritative["roles/cloudasset.owner"]:
+ condition: []
+ members:
+ - group:gcp-security-admins@example.com
+ project: foo-project-example
+ role: roles/cloudasset.owner
+ module.project.google_project_iam_binding.authoritative["roles/cloudsupport.techSupportEditor"]:
+ condition: []
+ members:
+ - group:gcp-security-admins@example.com
+ project: foo-project-example
+ role: roles/cloudsupport.techSupportEditor
+ module.project.google_project_iam_binding.authoritative["roles/iam.securityReviewer"]:
+ condition: []
+ members:
+ - group:gcp-security-admins@example.com
+ project: foo-project-example
+ role: roles/iam.securityReviewer
+ module.project.google_project_iam_binding.authoritative["roles/logging.admin"]:
+ condition: []
+ members:
+ - group:gcp-security-admins@example.com
+ project: foo-project-example
+ role: roles/logging.admin
+
+counts:
+ google_project: 1
+ google_project_iam_binding: 4
diff --git a/tests/modules/project/examples/kms.yaml b/tests/modules/project/examples/kms.yaml
new file mode 100644
index 000000000..b3981881a
--- /dev/null
+++ b/tests/modules/project/examples/kms.yaml
@@ -0,0 +1,38 @@
+# 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.
+
+values:
+ module.org.google_tags_tag_key.default["environment"]:
+ description: Environment specification.
+ parent: organizations/1122334455
+ purpose: null
+ purpose_data: null
+ short_name: environment
+ module.org.google_tags_tag_value.default["environment/dev"]:
+ description: Managed by the Terraform organization module.
+ short_name: dev
+ module.org.google_tags_tag_value.default["environment/prod"]:
+ description: Managed by the Terraform organization module.
+ short_name: prod
+ module.project.google_project.project[0]:
+ project_id: test-project
+ module.project.google_tags_tag_binding.binding["env-prod"]: {}
+ module.project.google_tags_tag_binding.binding["foo"]:
+ tag_value: tagValues/12345678
+
+counts:
+ google_project: 1
+ google_tags_tag_binding: 2
+ google_tags_tag_key: 1
+ google_tags_tag_value: 2
diff --git a/tests/modules/project/examples/logging.yaml b/tests/modules/project/examples/logging.yaml
new file mode 100644
index 000000000..9902c0adc
--- /dev/null
+++ b/tests/modules/project/examples/logging.yaml
@@ -0,0 +1,94 @@
+# 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.
+
+values:
+ module.project-host.google_bigquery_dataset_iam_member.bq-sinks-binding["info"]:
+ condition: []
+ role: roles/bigquery.dataEditor
+ module.project-host.google_logging_project_exclusion.logging-exclusion["no-gce-instances"]:
+ description: no-gce-instances (Terraform-managed).
+ disabled: null
+ filter: resource.type=gce_instance
+ name: no-gce-instances
+ project: my-project
+ module.project-host.google_logging_project_sink.sink["debug"]:
+ description: debug (Terraform-managed).
+ disabled: false
+ exclusions:
+ - description: null
+ disabled: false
+ filter: logName:compute
+ name: no-compute
+ filter: severity=DEBUG
+ name: debug
+ project: my-project
+ unique_writer_identity: false
+ module.project-host.google_logging_project_sink.sink["info"]:
+ description: info (Terraform-managed).
+ disabled: false
+ exclusions: []
+ filter: severity=INFO
+ name: info
+ project: my-project
+ unique_writer_identity: false
+ module.project-host.google_logging_project_sink.sink["notice"]:
+ description: notice (Terraform-managed).
+ disabled: false
+ exclusions: []
+ filter: severity=NOTICE
+ name: notice
+ project: my-project
+ unique_writer_identity: false
+ module.project-host.google_logging_project_sink.sink["warnings"]:
+ description: warnings (Terraform-managed).
+ destination: storage.googleapis.com/gcs_sink
+ disabled: false
+ exclusions: []
+ filter: severity=WARNING
+ name: warnings
+ project: my-project
+ unique_writer_identity: false
+ module.project-host.google_project.project[0]:
+ auto_create_network: false
+ billing_account: 123456-123456-123456
+ folder_id: '1234567890'
+ labels: null
+ name: my-project
+ org_id: null
+ project_id: my-project
+ skip_delete: false
+ module.project-host.google_project_iam_member.bucket-sinks-binding["debug"]:
+ condition:
+ - title: debug bucket writer
+ role: roles/logging.bucketWriter
+ module.project-host.google_pubsub_topic_iam_member.pubsub-sinks-binding["notice"]:
+ condition: []
+ role: roles/pubsub.publisher
+ module.project-host.google_storage_bucket_iam_member.gcs-sinks-binding["warnings"]:
+ bucket: gcs_sink
+ condition: []
+ role: roles/storage.objectCreator
+
+counts:
+ google_bigquery_dataset: 1
+ google_bigquery_dataset_iam_member: 1
+ google_logging_project_bucket_config: 1
+ google_logging_project_exclusion: 1
+ google_logging_project_sink: 4
+ google_project: 1
+ google_project_iam_member: 1
+ google_pubsub_topic: 1
+ google_pubsub_topic_iam_member: 1
+ google_storage_bucket: 1
+ google_storage_bucket_iam_member: 1
diff --git a/tests/modules/project/examples/org-policies.yaml b/tests/modules/project/examples/org-policies.yaml
new file mode 100644
index 000000000..8841dedee
--- /dev/null
+++ b/tests/modules/project/examples/org-policies.yaml
@@ -0,0 +1,125 @@
+# 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.
+
+values:
+ module.project.google_org_policy_policy.default["compute.disableGuestAttributesAccess"]:
+ name: projects/foo-project-example/policies/compute.disableGuestAttributesAccess
+ parent: projects/foo-project-example
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ module.project.google_org_policy_policy.default["constraints/compute.skipDefaultNetworkCreation"]:
+ name: projects/foo-project-example/policies/constraints/compute.skipDefaultNetworkCreation
+ parent: projects/foo-project-example
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ module.project.google_org_policy_policy.default["constraints/compute.trustedImageProjects"]:
+ name: projects/foo-project-example/policies/constraints/compute.trustedImageProjects
+ parent: projects/foo-project-example
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: null
+ values:
+ - allowed_values:
+ - projects/my-project
+ denied_values: null
+ module.project.google_org_policy_policy.default["constraints/compute.vmExternalIpAccess"]:
+ name: projects/foo-project-example/policies/constraints/compute.vmExternalIpAccess
+ parent: projects/foo-project-example
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: 'TRUE'
+ enforce: null
+ values: []
+ module.project.google_org_policy_policy.default["constraints/iam.allowedPolicyMemberDomains"]:
+ name: projects/foo-project-example/policies/constraints/iam.allowedPolicyMemberDomains
+ parent: projects/foo-project-example
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: null
+ values:
+ - allowed_values:
+ - C0xxxxxxx
+ - C0yyyyyyy
+ denied_values: null
+ module.project.google_org_policy_policy.default["iam.disableServiceAccountKeyCreation"]:
+ name: projects/foo-project-example/policies/iam.disableServiceAccountKeyCreation
+ parent: projects/foo-project-example
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ module.project.google_org_policy_policy.default["iam.disableServiceAccountKeyUpload"]:
+ name: projects/foo-project-example/policies/iam.disableServiceAccountKeyUpload
+ parent: projects/foo-project-example
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition:
+ - description: test condition
+ expression: resource.matchTagId("tagKeys/1234", "tagValues/1234")
+ location: somewhere
+ title: condition
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'FALSE'
+ values: []
+ module.project.google_project.project[0]:
+ billing_account: 123456-123456-123456
+ folder_id: '1234567890'
+ name: foo-project-example
+ org_id: null
+ project_id: foo-project-example
+
+counts:
+ google_org_policy_policy: 7
+ google_project: 1
diff --git a/tests/modules/project/examples/outputs.yaml b/tests/modules/project/examples/outputs.yaml
new file mode 100644
index 000000000..339896625
--- /dev/null
+++ b/tests/modules/project/examples/outputs.yaml
@@ -0,0 +1,27 @@
+# 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.
+
+values:
+ module.project.google_project.project[0]:
+ project_id: project-example
+ module.project.google_project_service.project_services["compute.googleapis.com"]:
+ project: project-example
+ service: compute.googleapis.com
+
+counts:
+ google_project: 1
+ google_project_service: 1
+
+outputs:
+ compute_robot: __missing__
diff --git a/tests/modules/project/examples/shared-vpc.yaml b/tests/modules/project/examples/shared-vpc.yaml
new file mode 100644
index 000000000..b03f220af
--- /dev/null
+++ b/tests/modules/project/examples/shared-vpc.yaml
@@ -0,0 +1,46 @@
+# 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.
+
+values:
+ module.host-project.google_compute_shared_vpc_host_project.shared_vpc_host[0]:
+ project: my-host-project
+ module.host-project.google_project.project[0]:
+ project_id: my-host-project
+ module.service-project.google_compute_shared_vpc_service_project.shared_vpc_service[0]:
+ host_project: my-host-project
+ service_project: my-service-project
+ module.service-project.google_project.project[0]:
+ project_id: my-service-project
+ module.service-project.google_project_iam_member.shared_vpc_host_robots["roles/compute.networkUser:cloudservices"]:
+ condition: []
+ project: my-host-project
+ role: roles/compute.networkUser
+ module.service-project.google_project_iam_member.shared_vpc_host_robots["roles/compute.networkUser:container-engine"]:
+ condition: []
+ project: my-host-project
+ role: roles/compute.networkUser
+ module.service-project.google_project_iam_member.shared_vpc_host_robots["roles/container.hostServiceAgentUser:container-engine"]:
+ condition: []
+ project: my-host-project
+ role: roles/container.hostServiceAgentUser
+ module.service-project.google_project_iam_member.shared_vpc_host_robots["roles/vpcaccess.user:cloudrun"]:
+ condition: []
+ project: my-host-project
+ role: roles/vpcaccess.user
+
+counts:
+ google_compute_shared_vpc_host_project: 1
+ google_compute_shared_vpc_service_project: 1
+ google_project: 2
+ google_project_iam_member: 4
diff --git a/tests/modules/project/fixture/main.tf b/tests/modules/project/fixture/main.tf
deleted file mode 100644
index 08cf49dc6..000000000
--- a/tests/modules/project/fixture/main.tf
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * 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 "test" {
- source = "../../../../modules/project"
- name = var.name
- billing_account = var.billing_account
- auto_create_network = var.auto_create_network
- custom_roles = var.custom_roles
- iam = var.iam
- iam_additive = var.iam_additive
- iam_additive_members = var.iam_additive_members
- labels = var.labels
- lien_reason = var.lien_reason
- org_policies = var.org_policies
- org_policies_data_path = var.org_policies_data_path
- oslogin = var.oslogin
- oslogin_admins = var.oslogin_admins
- oslogin_users = var.oslogin_users
- parent = var.parent
- prefix = var.prefix
- service_encryption_key_ids = var.service_encryption_key_ids
- services = var.services
- logging_sinks = var.logging_sinks
- logging_exclusions = var.logging_exclusions
- shared_vpc_host_config = var.shared_vpc_host_config
-}
-
-module "test-svpc-service" {
- source = "../../../../modules/project"
- count = var._test_service_project ? 1 : 0
- name = "test-svc"
- billing_account = var.billing_account
- auto_create_network = false
- parent = var.parent
- services = var.services
- shared_vpc_service_config = {
- attach = true
- host_project = module.test.project_id
- service_identity_iam = {
- "roles/compute.networkUser" = [
- "cloudservices", "container-engine"
- ]
- "roles/vpcaccess.user" = [
- "cloudrun"
- ]
- "roles/container.hostServiceAgentUser" = [
- "container-engine"
- ]
- }
- }
-}
diff --git a/tests/modules/project/fixture/test.logging-sinks.tfvars b/tests/modules/project/fixture/test.logging-sinks.tfvars
deleted file mode 100644
index 5c79cfb55..000000000
--- a/tests/modules/project/fixture/test.logging-sinks.tfvars
+++ /dev/null
@@ -1,29 +0,0 @@
-logging_sinks = {
- warning = {
- destination = "mybucket"
- type = "storage"
- filter = "severity=WARNING"
- }
- info = {
- destination = "projects/myproject/datasets/mydataset"
- type = "bigquery"
- filter = "severity=INFO"
- disabled = true
- }
- notice = {
- destination = "projects/myproject/topics/mytopic"
- type = "pubsub"
- filter = "severity=NOTICE"
- unique_writer = true
- }
- debug = {
- destination = "projects/myproject/locations/global/buckets/mybucket"
- type = "logging"
- filter = "severity=DEBUG"
- exclusions = {
- no-compute = "logName:compute"
- no-container = "logName:container"
- }
- unique_writer = true
- }
-}
diff --git a/tests/modules/project/fixture/variables.tf b/tests/modules/project/fixture/variables.tf
deleted file mode 100644
index 4c3474f00..000000000
--- a/tests/modules/project/fixture/variables.tf
+++ /dev/null
@@ -1,134 +0,0 @@
-/**
- * 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 "_test_service_project" {
- type = bool
- default = false
-}
-
-variable "name" {
- type = string
- default = "my-project"
-}
-
-variable "billing_account" {
- type = string
- default = "12345-12345-12345"
-}
-
-variable "auto_create_network" {
- type = bool
- default = false
-}
-
-variable "custom_roles" {
- type = map(list(string))
- default = {}
-}
-
-variable "iam" {
- type = map(list(string))
- default = {}
-}
-
-variable "iam_additive" {
- type = map(list(string))
- default = {}
-}
-
-variable "iam_additive_members" {
- type = map(list(string))
- default = {}
-}
-
-variable "labels" {
- type = map(string)
- default = {}
-}
-
-variable "lien_reason" {
- type = string
- default = ""
-}
-
-variable "org_policies" {
- type = any
- default = {}
-}
-
-variable "org_policies_data_path" {
- type = any
- default = null
-}
-
-variable "oslogin" {
- type = bool
- default = false
-}
-
-variable "oslogin_admins" {
- type = list(string)
- default = []
-}
-
-variable "oslogin_users" {
- type = list(string)
- default = []
-}
-
-variable "parent" {
- type = string
- default = null
-}
-
-variable "prefix" {
- type = string
- default = null
-}
-
-variable "service_encryption_key_ids" {
- type = map(list(string))
- default = {}
-}
-
-variable "services" {
- type = list(string)
- default = []
-}
-
-variable "logging_sinks" {
- type = any
- default = {}
-}
-
-variable "logging_exclusions" {
- type = map(string)
- default = {}
-}
-
-variable "shared_vpc_host_config" {
- type = object({
- enabled = bool
- service_projects = list(string)
- })
- default = {
- enabled = true
- service_projects = [
- "my-service-project-1",
- "my-service-project-2"
- ]
- }
-}
diff --git a/tests/modules/project/no_parent.tfvars b/tests/modules/project/no_parent.tfvars
new file mode 100644
index 000000000..8b954bdef
--- /dev/null
+++ b/tests/modules/project/no_parent.tfvars
@@ -0,0 +1 @@
+parent = null
diff --git a/tests/modules/project/no_parent.yaml b/tests/modules/project/no_parent.yaml
new file mode 100644
index 000000000..57f2fbd49
--- /dev/null
+++ b/tests/modules/project/no_parent.yaml
@@ -0,0 +1,22 @@
+# 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.
+
+values:
+ google_project.project[0]:
+ project_id: my-project
+ folder_id: null
+ org_id: null
+
+counts:
+ google_project: 1
diff --git a/tests/modules/project/no_prefix.tfvars b/tests/modules/project/no_prefix.tfvars
new file mode 100644
index 000000000..3d1b7ab3e
--- /dev/null
+++ b/tests/modules/project/no_prefix.tfvars
@@ -0,0 +1 @@
+prefix = null
diff --git a/tests/modules/project/no_prefix.yaml b/tests/modules/project/no_prefix.yaml
new file mode 100644
index 000000000..6322ca9c1
--- /dev/null
+++ b/tests/modules/project/no_prefix.yaml
@@ -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.
+
+values:
+ google_project.project[0]:
+ project_id: my-project
+
+counts:
+ google_project: 1
diff --git a/tests/modules/project/fixture/test.orgpolicies-boolean.tfvars b/tests/modules/project/org_policies_boolean.tfvars
similarity index 100%
rename from tests/modules/project/fixture/test.orgpolicies-boolean.tfvars
rename to tests/modules/project/org_policies_boolean.tfvars
diff --git a/tests/modules/project/org_policies_boolean.yaml b/tests/modules/project/org_policies_boolean.yaml
new file mode 100644
index 000000000..4f23958fb
--- /dev/null
+++ b/tests/modules/project/org_policies_boolean.yaml
@@ -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.
+
+values:
+ google_org_policy_policy.default["iam.disableServiceAccountKeyCreation"]:
+ name: projects/my-project/policies/iam.disableServiceAccountKeyCreation
+ parent: projects/my-project
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ timeouts: null
+ google_org_policy_policy.default["iam.disableServiceAccountKeyUpload"]:
+ name: projects/my-project/policies/iam.disableServiceAccountKeyUpload
+ parent: projects/my-project
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition:
+ - description: test condition
+ expression: resource.matchTagId(aa, bb)
+ location: xxx
+ title: condition
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'FALSE'
+ values: []
+ timeouts: null
+
+counts:
+ google_org_policy_policy: 2
diff --git a/tests/modules/project/fixture/test.orgpolicies-list.tfvars b/tests/modules/project/org_policies_list.tfvars
similarity index 96%
rename from tests/modules/project/fixture/test.orgpolicies-list.tfvars
rename to tests/modules/project/org_policies_list.tfvars
index 738071733..f9de8dbab 100644
--- a/tests/modules/project/fixture/test.orgpolicies-list.tfvars
+++ b/tests/modules/project/org_policies_list.tfvars
@@ -3,6 +3,7 @@ org_policies = {
deny = { all = true }
}
"iam.allowedPolicyMemberDomains" = {
+ inherit_from_parent = true
allow = {
values = ["C0xxxxxxx", "C0yyyyyyy"]
}
diff --git a/tests/modules/project/org_policies_list.yaml b/tests/modules/project/org_policies_list.yaml
new file mode 100644
index 000000000..2f1c64e0b
--- /dev/null
+++ b/tests/modules/project/org_policies_list.yaml
@@ -0,0 +1,85 @@
+# 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.
+
+values:
+ google_org_policy_policy.default["compute.restrictLoadBalancerCreationForTypes"]:
+ name: projects/my-project/policies/compute.restrictLoadBalancerCreationForTypes
+ parent: projects/my-project
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition:
+ - description: test condition
+ expression: resource.matchTagId(aa, bb)
+ location: xxx
+ title: condition
+ deny_all: null
+ enforce: null
+ values:
+ - allowed_values:
+ - EXTERNAL_1
+ denied_values: null
+ - allow_all: 'TRUE'
+ condition:
+ - description: test condition2
+ expression: resource.matchTagId(cc, dd)
+ location: xxx
+ title: condition2
+ deny_all: null
+ enforce: null
+ values: []
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: null
+ values:
+ - allowed_values: null
+ denied_values:
+ - in:EXTERNAL
+ timeouts: null
+ google_org_policy_policy.default["compute.vmExternalIpAccess"]:
+ name: projects/my-project/policies/compute.vmExternalIpAccess
+ parent: projects/my-project
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: 'TRUE'
+ enforce: null
+ values: []
+ timeouts: null
+ google_org_policy_policy.default["iam.allowedPolicyMemberDomains"]:
+ name: projects/my-project/policies/iam.allowedPolicyMemberDomains
+ parent: projects/my-project
+ spec:
+ - inherit_from_parent: true
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: null
+ values:
+ - allowed_values:
+ - C0xxxxxxx
+ - C0yyyyyyy
+ denied_values: null
+ timeouts: null
+
+counts:
+ google_org_policy_policy: 3
diff --git a/tests/modules/project/parent_folder.tfvars b/tests/modules/project/parent_folder.tfvars
new file mode 100644
index 000000000..e7ce6acda
--- /dev/null
+++ b/tests/modules/project/parent_folder.tfvars
@@ -0,0 +1 @@
+parent = "folders/12345678"
diff --git a/tests/modules/api_gateway/test_plan.py b/tests/modules/project/parent_folder.yaml
similarity index 80%
rename from tests/modules/api_gateway/test_plan.py
rename to tests/modules/project/parent_folder.yaml
index 18ecdd329..684f94d81 100644
--- a/tests/modules/api_gateway/test_plan.py
+++ b/tests/modules/project/parent_folder.yaml
@@ -12,8 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+values:
+ google_project.project[0]:
+ project_id: my-project
+ folder_id: '12345678'
+ org_id: null
-def test_resource_count(plan_runner):
- "Test number of resources created."
- _, resources = plan_runner()
- assert len(resources) == 5
+counts:
+ google_project: 1
diff --git a/tests/modules/project/parent_org.tfvars b/tests/modules/project/parent_org.tfvars
new file mode 100644
index 000000000..86ce1eec6
--- /dev/null
+++ b/tests/modules/project/parent_org.tfvars
@@ -0,0 +1 @@
+parent = "organizations/12345678"
diff --git a/tests/modules/project/parent_org.yaml b/tests/modules/project/parent_org.yaml
new file mode 100644
index 000000000..ded3f6f30
--- /dev/null
+++ b/tests/modules/project/parent_org.yaml
@@ -0,0 +1,22 @@
+# 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.
+
+values:
+ google_project.project[0]:
+ project_id: my-project
+ folder_id: null
+ org_id: '12345678'
+
+counts:
+ google_project: 1
diff --git a/tests/modules/project/prefix.tfvars b/tests/modules/project/prefix.tfvars
new file mode 100644
index 000000000..0031d561d
--- /dev/null
+++ b/tests/modules/project/prefix.tfvars
@@ -0,0 +1 @@
+prefix = "foo"
diff --git a/tests/modules/project/prefix.yaml b/tests/modules/project/prefix.yaml
new file mode 100644
index 000000000..e5126e200
--- /dev/null
+++ b/tests/modules/project/prefix.yaml
@@ -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.
+
+values:
+ google_project.project[0]:
+ project_id: foo-my-project
+
+counts:
+ google_project: 1
diff --git a/tests/modules/project/service_encryption_keys.tfvars b/tests/modules/project/service_encryption_keys.tfvars
new file mode 100644
index 000000000..f0dedc21a
--- /dev/null
+++ b/tests/modules/project/service_encryption_keys.tfvars
@@ -0,0 +1,4 @@
+service_encryption_key_ids = {
+ compute = ["key1"],
+ storage = ["key1", "key2"]
+}
diff --git a/tests/modules/project/service_encryption_keys.yaml b/tests/modules/project/service_encryption_keys.yaml
new file mode 100644
index 000000000..8e2bd8236
--- /dev/null
+++ b/tests/modules/project/service_encryption_keys.yaml
@@ -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.
+
+values:
+ google_kms_crypto_key_iam_member.service_identity_cmek["compute.key1"]:
+ condition: []
+ crypto_key_id: key1
+ role: roles/cloudkms.cryptoKeyEncrypterDecrypter
+ google_kms_crypto_key_iam_member.service_identity_cmek["storage.key1"]:
+ condition: []
+ crypto_key_id: key1
+ role: roles/cloudkms.cryptoKeyEncrypterDecrypter
+ google_kms_crypto_key_iam_member.service_identity_cmek["storage.key2"]:
+ condition: []
+ crypto_key_id: key2
+ role: roles/cloudkms.cryptoKeyEncrypterDecrypter
+ google_project.project[0]:
+ auto_create_network: false
+ billing_account: null
+ folder_id: null
+ labels: null
+ name: my-project
+ org_id: null
+ project_id: my-project
+ skip_delete: false
+ timeouts: null
+
+counts:
+ google_kms_crypto_key_iam_member: 3
+ google_project: 1
+
+outputs:
+ name: my-project
diff --git a/tests/modules/project/test_iam.py b/tests/modules/project/test_iam.py
deleted file mode 100644
index 16581baa9..000000000
--- a/tests/modules/project/test_iam.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# 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_iam(plan_runner):
- "Test IAM bindings."
- iam = (
- '{"roles/owner" = ["user:one@example.org"],'
- '"roles/viewer" = ["user:two@example.org", "user:three@example.org"]}'
- )
- _, resources = plan_runner(iam=iam)
- roles = dict((r['values']['role'], r['values']['members'])
- for r in resources if r['type'] == 'google_project_iam_binding')
- assert roles == {
- 'roles/owner': ['user:one@example.org'],
- 'roles/viewer': ['user:three@example.org', 'user:two@example.org']}
-
-
-def test_iam_additive(plan_runner):
- "Test IAM additive bindings."
- iam = (
- '{"roles/owner" = ["user:one@example.org"],'
- '"roles/viewer" = ["user:two@example.org", "user:three@example.org"]}'
- )
- _, resources = plan_runner(iam_additive=iam)
- roles = set((r['values']['role'], r['values']['member'])
- for r in resources if r['type'] == 'google_project_iam_member')
- assert roles == set([
- ('roles/owner', 'user:one@example.org'),
- ('roles/viewer', 'user:three@example.org'),
- ('roles/viewer', 'user:two@example.org')
- ])
-
-
-def test_iam_additive_members(plan_runner):
- "Test IAM additive members."
- iam = (
- '{"user:one@example.org" = ["roles/owner"],'
- '"user:two@example.org" = ["roles/owner", "roles/editor"]}'
- )
- _, resources = plan_runner(iam_additive_members=iam)
- roles = set((r['values']['role'], r['values']['member'])
- for r in resources if r['type'] == 'google_project_iam_member')
- assert roles == set([
- ('roles/owner', 'user:one@example.org'),
- ('roles/owner', 'user:two@example.org'),
- ('roles/editor', 'user:two@example.org')
- ])
diff --git a/tests/modules/project/test_plan.py b/tests/modules/project/test_plan.py
index 8d1bd538c..50d50b3c7 100644
--- a/tests/modules/project/test_plan.py
+++ b/tests/modules/project/test_plan.py
@@ -12,65 +12,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-def test_prefix(plan_runner):
- "Test project id prefix."
- _, resources = plan_runner()
- assert len(resources) == 4
- [project_resource] = [r for r in resources if r['address'] == 'module.test.google_project.project[0]']
- assert project_resource['values']['name'] == 'my-project'
- _, resources = plan_runner(prefix='foo')
- assert len(resources) == 4
- [project_resource] = [r for r in resources if r['address'] == 'module.test.google_project.project[0]']
- assert project_resource['values']['name'] == 'foo-my-project'
-
-
-def test_parent(plan_runner):
- "Test project parent."
- _, resources = plan_runner(parent='folders/12345678')
- assert len(resources) == 4
- [project_resource] = [r for r in resources if r['address'] == 'module.test.google_project.project[0]']
- assert project_resource['values']['folder_id'] == '12345678'
- assert project_resource['values'].get('org_id') == None
- _, resources = plan_runner(parent='organizations/12345678')
- assert len(resources) == 4
- [project_resource] = [r for r in resources if r['address'] == 'module.test.google_project.project[0]']
- assert project_resource['values']['org_id'] == '12345678'
- assert project_resource['values'].get('folder_id') == None
-
-
-def test_no_parent(plan_runner):
- "Test null project parent."
- _, resources = plan_runner()
- assert len(resources) == 4
- [project_resource] = [r for r in resources if r['address'] == 'module.test.google_project.project[0]']
- assert project_resource['values'].get('folder_id') == None
- assert project_resource['values'].get('org_id') == None
-
-
-def test_service_encryption_keys(plan_runner):
- "Test service encryption keys with no dependencies."
- _, resources = plan_runner(service_encryption_key_ids=(
- '{compute=["key1"], storage=["key1", "key2"]}'
- ))
- key_bindings = [
- r['index'] for r in resources
- if r['type'] == 'google_kms_crypto_key_iam_member'
- ]
- assert len(key_bindings), 3
- assert key_bindings == ['compute.key1', 'storage.key1', 'storage.key2']
-
-
-def test_service_encryption_key_dependencies(plan_runner):
- "Test service encryption keys with dependencies."
- _, resources = plan_runner(service_encryption_key_ids=(
- '{compute=["key1"], dataflow=["key1", "key2"]}'
- ))
- key_bindings = [
- r['index'] for r in resources
- if r['type'] == 'google_kms_crypto_key_iam_member'
- ]
- assert len(key_bindings), 3
- # compute.key1 cannot repeat or we'll get a duplicate key error in for_each
- assert key_bindings == [
- 'compute.key1', 'compute.key2', 'dataflow.key1', 'dataflow.key2'
- ]
+# def test_service_encryption_key_dependencies(plan_runner):
+# "Test service encryption keys with dependencies."
+# _, resources = plan_runner(service_encryption_key_ids=(
+# '{compute=["key1"], dataflow=["key1", "key2"]}'))
+# key_bindings = [
+# r['index']
+# for r in resources
+# if r['type'] == 'google_kms_crypto_key_iam_member'
+# ]
+# assert len(key_bindings), 3
+# # compute.key1 cannot repeat or we'll get a duplicate key error in for_each
+# assert key_bindings == [
+# 'compute.key1', 'compute.key2', 'dataflow.key1', 'dataflow.key2'
+# ]
diff --git a/tests/modules/project/test_plan_logging.py b/tests/modules/project/test_plan_logging.py
deleted file mode 100644
index 59c9179bc..000000000
--- a/tests/modules/project/test_plan_logging.py
+++ /dev/null
@@ -1,126 +0,0 @@
-# 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.
-
-from collections import Counter
-
-
-def test_sinks(plan_runner):
- "Test folder-level sinks."
- tfvars = 'test.logging-sinks.tfvars'
- _, resources = plan_runner(tf_var_file=tfvars)
- assert len(resources) == 12
-
- resource_types = Counter([r["type"] for r in resources])
- assert resource_types == {
- "google_logging_project_sink": 4,
- "google_bigquery_dataset_iam_member": 1,
- "google_project": 1,
- "google_project_iam_member": 1,
- "google_pubsub_topic_iam_member": 1,
- "google_storage_bucket_iam_member": 1,
- "google_compute_shared_vpc_host_project": 1,
- "google_compute_shared_vpc_service_project": 2
- }
-
- sinks = [r for r in resources if r["type"] == "google_logging_project_sink"]
- assert sorted([r["index"] for r in sinks]) == [
- "debug",
- "info",
- "notice",
- "warning",
- ]
- values = [(
- r["index"],
- r["values"]["filter"],
- r["values"]["destination"],
- r["values"]["unique_writer_identity"],
- ) for r in sinks]
- assert sorted(values) == [
- (
- "debug",
- "severity=DEBUG",
- "logging.googleapis.com/projects/myproject/locations/global/buckets/mybucket",
- True,
- ),
- (
- "info",
- "severity=INFO",
- "bigquery.googleapis.com/projects/myproject/datasets/mydataset",
- False,
- ),
- (
- "notice",
- "severity=NOTICE",
- "pubsub.googleapis.com/projects/myproject/topics/mytopic",
- True,
- ),
- ("warning", "severity=WARNING", "storage.googleapis.com/mybucket", False),
- ]
-
- bindings = [r for r in resources if "member" in r["type"]]
- values = [(r["index"], r["type"], r["values"]["role"]) for r in bindings]
- assert sorted(values) == [
- ("debug", "google_project_iam_member", "roles/logging.bucketWriter"),
- ("info", "google_bigquery_dataset_iam_member",
- "roles/bigquery.dataEditor"),
- ("notice", "google_pubsub_topic_iam_member", "roles/pubsub.publisher"),
- ("warning", "google_storage_bucket_iam_member",
- "roles/storage.objectCreator"),
- ]
-
- exclusions = [(r["index"], r["values"]["exclusions"]) for r in sinks]
- assert sorted(exclusions) == [
- (
- "debug",
- [
- {
- "description": None,
- "disabled": False,
- "filter": "logName:compute",
- "name": "no-compute",
- },
- {
- "description": None,
- "disabled": False,
- "filter": "logName:container",
- "name": "no-container",
- },
- ],
- ),
- ("info", []),
- ("notice", []),
- ("warning", []),
- ]
-
-
-def test_exclusions(plan_runner):
- "Test folder-level logging exclusions."
- logging_exclusions = ("{"
- 'exclusion1 = "resource.type=gce_instance", '
- 'exclusion2 = "severity=NOTICE", '
- "}")
- _, resources = plan_runner(logging_exclusions=logging_exclusions)
- assert len(resources) == 6
- exclusions = [
- r for r in resources if r["type"] == "google_logging_project_exclusion"
- ]
- assert sorted([r["index"] for r in exclusions]) == [
- "exclusion1",
- "exclusion2",
- ]
- values = [(r["index"], r["values"]["filter"]) for r in exclusions]
- assert sorted(values) == [
- ("exclusion1", "resource.type=gce_instance"),
- ("exclusion2", "severity=NOTICE"),
- ]
diff --git a/tests/modules/project/test_plan_org_policies.py b/tests/modules/project/test_plan_org_policies.py
index 8463761e8..fef2a8aaf 100644
--- a/tests/modules/project/test_plan_org_policies.py
+++ b/tests/modules/project/test_plan_org_policies.py
@@ -12,33 +12,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from .validate_policies import validate_policy_boolean, validate_policy_list
+import pytest
+
+_params = ['boolean', 'list']
-def test_policy_boolean(plan_runner):
- "Test boolean org policy."
- tfvars = 'test.orgpolicies-boolean.tfvars'
- _, resources = plan_runner(tf_var_file=tfvars)
- validate_policy_boolean(resources)
-
-
-def test_policy_list(plan_runner):
- "Test list org policy."
- tfvars = 'test.orgpolicies-list.tfvars'
- _, resources = plan_runner(tf_var_file=tfvars)
- validate_policy_list(resources)
-
-
-def test_factory_policy_boolean(plan_runner, tfvars_to_yaml, tmp_path):
+@pytest.mark.parametrize('policy_type', _params)
+def test_policy_factory(plan_summary, tfvars_to_yaml, tmp_path, policy_type):
dest = tmp_path / 'policies.yaml'
- tfvars_to_yaml('fixture/test.orgpolicies-boolean.tfvars', dest,
- 'org_policies')
- _, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
- validate_policy_boolean(resources)
-
-
-def test_factory_policy_list(plan_runner, tfvars_to_yaml, tmp_path):
- dest = tmp_path / 'policies.yaml'
- tfvars_to_yaml('fixture/test.orgpolicies-list.tfvars', dest, 'org_policies')
- _, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
- validate_policy_list(resources)
+ tfvars_to_yaml(f'org_policies_{policy_type}.tfvars', dest, 'org_policies')
+ tfvars_plan = plan_summary(
+ 'modules/project',
+ tf_var_files=['common.tfvars', f'org_policies_{policy_type}.tfvars'])
+ yaml_plan = plan_summary('modules/project', tf_var_files=['common.tfvars'],
+ org_policies_data_path=f'{tmp_path}')
+ assert tfvars_plan.values == yaml_plan.values
diff --git a/tests/modules/project/test_plan_svpc.py b/tests/modules/project/test_plan_svpc.py
deleted file mode 100644
index bd22131d9..000000000
--- a/tests/modules/project/test_plan_svpc.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# 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.
-
-import os
-
-def test_svpc(_plan_runner):
- "Test Shared VPC service project attachment."
- fixture_path = os.path.join(os.path.dirname(__file__), 'fixture')
- plan = _plan_runner(fixture_path=fixture_path, _test_service_project='true')
- modules = [m for m in plan.root_module['child_modules']]
- resources = [r for r in modules[0]['resources'] if r['address'] == 'module.test.google_compute_shared_vpc_host_project.shared_vpc_host[0]']
- assert len(resources) == 1
- print(modules[1]['resources'])
- resources = [r for r in modules[1]['resources'] if r['address'] == 'module.test-svpc-service[0].google_compute_shared_vpc_service_project.shared_vpc_service[0]']
- assert len(resources) == 1
diff --git a/tests/blueprints/factories/bigquery_factory/test_plan.py b/tests/modules/project/tftest.yaml
similarity index 73%
rename from tests/blueprints/factories/bigquery_factory/test_plan.py
rename to tests/modules/project/tftest.yaml
index 74705e423..2fda31b7c 100644
--- a/tests/blueprints/factories/bigquery_factory/test_plan.py
+++ b/tests/modules/project/tftest.yaml
@@ -12,8 +12,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-def test_resources(e2e_plan_runner):
- "Test that plan works and the numbers of resources is as expected."
- modules, resources = e2e_plan_runner()
- assert len(modules) > 0
- assert len(resources) > 0
+module: modules/project
+
+common_tfvars:
+ - common.tfvars
+
+tests:
+ prefix:
+ no_prefix:
+ parent_folder:
+ parent_org:
+ no_parent:
+ service_encryption_keys:
+ org_policies_list:
+ org_policies_boolean:
diff --git a/tests/modules/project/validate_policies.py b/tests/modules/project/validate_policies.py
deleted file mode 100644
index 0fd038371..000000000
--- a/tests/modules/project/validate_policies.py
+++ /dev/null
@@ -1,160 +0,0 @@
-# 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 validate_policy_boolean(resources):
- assert len(resources) == 6
- policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
- assert len(policies) == 2
- assert all(x['values']['parent'] == 'projects/my-project' for x in policies)
-
- p1 = [
- r['values']['spec'][0]
- for r in policies
- if r['index'] == 'iam.disableServiceAccountKeyCreation'
- ][0]
-
- assert p1['inherit_from_parent'] is None
- assert p1['reset'] is None
- assert p1['rules'] == [{
- 'allow_all': None,
- 'condition': [],
- 'deny_all': None,
- 'enforce': 'TRUE',
- 'values': []
- }]
-
- p2 = [
- r['values']['spec'][0]
- for r in policies
- if r['index'] == 'iam.disableServiceAccountKeyUpload'
- ][0]
-
- assert p2['inherit_from_parent'] is None
- assert p2['reset'] is None
- assert len(p2['rules']) == 2
- assert p2['rules'][0] == {
- 'allow_all': None,
- 'condition': [],
- 'deny_all': None,
- 'enforce': 'FALSE',
- 'values': []
- }
- assert p2['rules'][1] == {
- 'allow_all': None,
- 'condition': [{
- 'description': 'test condition',
- 'expression': 'resource.matchTagId(aa, bb)',
- 'location': 'xxx',
- 'title': 'condition'
- }],
- 'deny_all': None,
- 'enforce': 'TRUE',
- 'values': []
- }
-
-
-def validate_policy_list(resources):
- assert len(resources) == 7
-
- policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
- assert len(policies) == 3
- assert all(x['values']['parent'] == 'projects/my-project' for x in policies)
-
- p1 = [
- r['values']['spec'][0]
- for r in policies
- if r['index'] == 'compute.vmExternalIpAccess'
- ][0]
- assert p1['inherit_from_parent'] is None
- assert p1['reset'] is None
- assert p1['rules'] == [{
- 'allow_all': None,
- 'condition': [],
- 'deny_all': 'TRUE',
- 'enforce': None,
- 'values': []
- }]
-
- p2 = [
- r['values']['spec'][0]
- for r in policies
- if r['index'] == 'iam.allowedPolicyMemberDomains'
- ][0]
- assert p2['inherit_from_parent'] is None
- assert p2['reset'] is None
- assert p2['rules'] == [{
- 'allow_all':
- None,
- 'condition': [],
- 'deny_all':
- None,
- 'enforce':
- None,
- 'values': [{
- 'allowed_values': [
- 'C0xxxxxxx',
- 'C0yyyyyyy',
- ],
- 'denied_values': None
- }]
- }]
-
- p3 = [
- r['values']['spec'][0]
- for r in policies
- if r['index'] == 'compute.restrictLoadBalancerCreationForTypes'
- ][0]
- assert p3['inherit_from_parent'] is None
- assert p3['reset'] is None
- assert len(p3['rules']) == 3
- assert p3['rules'][0] == {
- 'allow_all': None,
- 'condition': [],
- 'deny_all': None,
- 'enforce': None,
- 'values': [{
- 'allowed_values': None,
- 'denied_values': ['in:EXTERNAL']
- }]
- }
-
- assert p3['rules'][1] == {
- 'allow_all': None,
- 'condition': [{
- 'description': 'test condition',
- 'expression': 'resource.matchTagId(aa, bb)',
- 'location': 'xxx',
- 'title': 'condition'
- }],
- 'deny_all': None,
- 'enforce': None,
- 'values': [{
- 'allowed_values': ['EXTERNAL_1'],
- 'denied_values': None
- }]
- }
-
- assert p3['rules'][2] == {
- 'allow_all': 'TRUE',
- 'condition': [{
- 'description': 'test condition2',
- 'expression': 'resource.matchTagId(cc, dd)',
- 'location': 'xxx',
- 'title': 'condition2'
- }],
- 'deny_all': None,
- 'enforce': None,
- 'values': []
- }
diff --git a/tests/requirements.txt b/tests/requirements.txt
index a6f82d750..1e0921c19 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -1,6 +1,6 @@
-pytest>=6.2.5
+pytest>=7.2.1
PyYAML>=6.0
tftest>=1.8.1
-marko>=1.2.0
-deepdiff>=5.7.0
-python-hcl2>=3.0.5
+marko>=1.2.2
+deepdiff>=6.2.3
+python-hcl2>=4.3.0
diff --git a/tools/check_documentation.py b/tools/check_documentation.py
index 30e765718..47643493b 100755
--- a/tools/check_documentation.py
+++ b/tools/check_documentation.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
-# Copyright 2022 Google LLC
+# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -128,14 +128,14 @@ def _check_dir(dir_name, exclude_files=None, files=False, show_extra=False):
elif nc := [v.name for v in newvars if not v.description.endswith('.')]:
state = state.FAIL_VARIABLE_PERIOD
diff = "\n".join([
- f'----- {mod_name} variables missing colons -----',
+ f'----- {mod_name} variable descriptions missing ending period -----',
', '.join(nc),
])
elif nc := [o.name for o in newouts if not o.description.endswith('.')]:
state = state.FAIL_VARIABLE_PERIOD
diff = "\n".join([
- f'----- {mod_name} outputs missing colons -----',
+ f'----- {mod_name} output descriptions missing ending period -----',
', '.join(nc),
])
diff --git a/tools/check_links.py b/tools/check_links.py
index 77dc61739..1e2759dfb 100755
--- a/tools/check_links.py
+++ b/tools/check_links.py
@@ -86,7 +86,7 @@ def main(dirs, external):
state = '✓' if all(l.valid for l in doc.links) else '✗'
print(f'[{state}] {doc.relpath} ({len(doc.links)})')
if state == '✗':
- error = [f'{dir_name}{doc.relpath}']
+ error = [f'{dir_name}/{doc.relpath}']
for l in doc.links:
if not l.valid:
error.append(f' - {l.dest}')
diff --git a/tools/plan_summary.py b/tools/plan_summary.py
index def79adb4..ae52c86cf 100755
--- a/tools/plan_summary.py
+++ b/tools/plan_summary.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
-# Copyright 2022 Google LLC
+# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,29 +15,48 @@
# limitations under the License.
import click
+import os
import sys
+import tempfile
import yaml
from pathlib import Path
-BASEDIR = Path(__file__).parents[1]
-sys.path.append(str(BASEDIR / 'tests'))
-
-import fixtures
+try:
+ import fixtures
+except ImportError:
+ BASEDIR = Path(__file__).parents[1]
+ sys.path.append(str(BASEDIR / 'tests'))
+ import fixtures
@click.command()
+@click.option('--example', default=False, is_flag=True)
@click.argument('module', type=click.Path(), nargs=1)
@click.argument('tfvars', type=click.Path(exists=True), nargs=-1)
-def main(module, tfvars):
- module = BASEDIR / module
- summary = fixtures.plan_summary(module, Path(), tfvars)
- print(yaml.dump({'values': summary.values}))
- print(yaml.dump({'counts': summary.counts}))
- outputs = {
- k: v.get('value', '__missing__') for k, v in summary.outputs.items()
- }
- print(yaml.dump({'outputs': outputs}))
+def main(example, module, tfvars):
+ try:
+ if example:
+ tmp_dir = tempfile.TemporaryDirectory()
+ tmp_path = Path(tmp_dir.name)
+ common_vars = BASEDIR / 'tests' / 'examples' / 'variables.tf'
+ (tmp_path / 'main.tf').symlink_to(module)
+ (tmp_path / 'variables.tf').symlink_to(common_vars)
+ (tmp_path / 'fabric').symlink_to(BASEDIR)
+ module = tmp_path
+ else:
+ module = BASEDIR / module
+
+ summary = fixtures.plan_summary(module, Path(), tfvars)
+ print(yaml.dump({'values': summary.values}))
+ print(yaml.dump({'counts': summary.counts}))
+ outputs = {
+ k: v.get('value', '__missing__') for k, v in summary.outputs.items()
+ }
+ print(yaml.dump({'outputs': outputs}))
+ finally:
+ if example:
+ tmp_dir.cleanup()
if __name__ == '__main__':