diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90d3ca09..7cf58eb7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,19 +4,61 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
-
+
+
+## [26.0.0] - 2023-09-18
+
### BLUEPRINTS
+- [[#1684](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1684)] **incompatible change:** Update resource-level IAM interface for kms and pubsub modules ([juliocc](https://github.com/juliocc))
+- [[#1682](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1682)] GKE cluster modules: add optional kube state metrics ([olliefr](https://github.com/olliefr))
+- [[#1681](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1681)] **incompatible change:** Embed subnet-level IAM in the variables controlling creation of subnets ([juliocc](https://github.com/juliocc))
+- [[#1680](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1680)] Upgrades to `monitoring_config` in `gke-cluster-*`, docs update, and cosmetics fixes to GKE cluster modules ([olliefr](https://github.com/olliefr))
+- [[#1679](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1679)] Add lineage on Minimal Data Platform blueprint ([lcaggio](https://github.com/lcaggio))
+- [[#1678](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1678)] Allow only one of `secondary_range_blocks` or `secondary_range_names` when creating GKE clusters. ([juliocc](https://github.com/juliocc))
+- [[#1671](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1671)] **incompatible change:** Fixed, added back environments to each instance, that way we can also… ([apichick](https://github.com/apichick))
+- [[#1662](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1662)] merge labels from data_merges in project factory ([Tutuchan](https://github.com/Tutuchan))
+- [[#1651](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1651)] add AIRFLOW_VAR_ prefix to environment variables in data-platform blueprints ([Tutuchan](https://github.com/Tutuchan))
+- [[#1642](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1642)] New phpIPAM serverless third parties solution in blueprints ([simonebruzzechesse](https://github.com/simonebruzzechesse))
+- [[#1654](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1654)] Fix project factory blueprint and fast stage ([LucaPrete](https://github.com/LucaPrete))
+- [[#1647](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1647)] Bump provider version to 4.80.0 ([juliocc](https://github.com/juliocc))
+- [[#1638](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1638)] gke-cluster-standard: change logging configuration ([olliefr](https://github.com/olliefr))
+- [[#1636](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1636)] Delete api gateway blueprint ([juliodiez](https://github.com/juliodiez))
+- [[#1607](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1607)] Trap requests timeout error in quota sync ([ludoo](https://github.com/ludoo))
- [[#1595](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1595)] **incompatible change:** IAM interface refactor ([ludoo](https://github.com/ludoo))
- [[#1601](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1601)] [Data Platform] Update README.md ([lcaggio](https://github.com/lcaggio))
### DOCUMENTATION
+- [[#1687](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1687)] Add IAM variables template to ADR ([juliocc](https://github.com/juliocc))
+- [[#1686](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1686)] CONTRIBUTING guide: fix broken links and update "running tests for specific examples" section ([olliefr](https://github.com/olliefr))
+- [[#1658](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1658)] **incompatible change:** Change type of `iam_bindings` variable to allow multiple conditional bindings ([ludoo](https://github.com/ludoo))
+- [[#1642](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1642)] New phpIPAM serverless third parties solution in blueprints ([simonebruzzechesse](https://github.com/simonebruzzechesse))
+- [[#1640](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1640)] Simplify linting output in workflow ([juliocc](https://github.com/juliocc))
+- [[#1636](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1636)] Delete api gateway blueprint ([juliodiez](https://github.com/juliodiez))
- [[#1595](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1595)] **incompatible change:** IAM interface refactor ([ludoo](https://github.com/ludoo))
### FAST
+- [[#1684](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1684)] **incompatible change:** Update resource-level IAM interface for kms and pubsub modules ([juliocc](https://github.com/juliocc))
+- [[#1685](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1685)] Fix psa routing variable in FAST net stages ([ludoo](https://github.com/ludoo))
+- [[#1682](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1682)] GKE cluster modules: add optional kube state metrics ([olliefr](https://github.com/olliefr))
+- [[#1681](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1681)] **incompatible change:** Embed subnet-level IAM in the variables controlling creation of subnets ([juliocc](https://github.com/juliocc))
+- [[#1680](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1680)] Upgrades to `monitoring_config` in `gke-cluster-*`, docs update, and cosmetics fixes to GKE cluster modules ([olliefr](https://github.com/olliefr))
+- [[#1678](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1678)] Allow only one of `secondary_range_blocks` or `secondary_range_names` when creating GKE clusters. ([juliocc](https://github.com/juliocc))
+- [[#1664](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1664)] Align pf stage sample data to new format ([ludoo](https://github.com/ludoo))
+- [[#1663](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1663)] [#1661] Make FAST stage 1 resman tf destroy more reliable ([LucaPrete](https://github.com/LucaPrete))
+- [[#1659](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1659)] Link project factory documentation from FAST stage ([ludoo](https://github.com/ludoo))
+- [[#1658](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1658)] **incompatible change:** Change type of `iam_bindings` variable to allow multiple conditional bindings ([ludoo](https://github.com/ludoo))
+- [[#1654](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1654)] Fix project factory blueprint and fast stage ([LucaPrete](https://github.com/LucaPrete))
+- [[#1638](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1638)] gke-cluster-standard: change logging configuration ([olliefr](https://github.com/olliefr))
+- [[#1634](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1634)] [revert(revert(patch))] Remove unused ASN numbers for CloudNAT in FAST ([LucaPrete](https://github.com/LucaPrete))
+- [[#1631](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1631)] Allow single hfw policy association in folder and organization modules ([juliocc](https://github.com/juliocc))
+- [[#1626](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1626)] Revert "Remove unused ASN numbers from CloudNAT to avoid provider errors" ([LucaPrete](https://github.com/LucaPrete))
+- [[#1623](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1623)] Fix role name for delegated grants in FAST bootstrap ([juliocc](https://github.com/juliocc))
+- [[#1612](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1612)] Fix: align stage-2-e-nva-bgp to the latest APIs ([LucaPrete](https://github.com/LucaPrete))
+- [[#1610](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1610)] Fix: use existing variable to optionally name fw policies ([LucaPrete](https://github.com/LucaPrete))
- [[#1595](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1595)] **incompatible change:** IAM interface refactor ([ludoo](https://github.com/ludoo))
- [[#1597](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1597)] fix null object exception in bootstrap output when using cloudsource ([sm3142](https://github.com/sm3142))
- [[#1593](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1593)] Fix FAST CI/CD for Gitlab ([ludoo](https://github.com/ludoo))
@@ -24,6 +66,41 @@ All notable changes to this project will be documented in this file.
### MODULES
+- [[#1684](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1684)] **incompatible change:** Update resource-level IAM interface for kms and pubsub modules ([juliocc](https://github.com/juliocc))
+- [[#1683](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1683)] Fix subnet iam_bindings to use arbitrary keys ([juliocc](https://github.com/juliocc))
+- [[#1682](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1682)] GKE cluster modules: add optional kube state metrics ([olliefr](https://github.com/olliefr))
+- [[#1681](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1681)] **incompatible change:** Embed subnet-level IAM in the variables controlling creation of subnets ([juliocc](https://github.com/juliocc))
+- [[#1680](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1680)] Upgrades to `monitoring_config` in `gke-cluster-*`, docs update, and cosmetics fixes to GKE cluster modules ([olliefr](https://github.com/olliefr))
+- [[#1678](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1678)] Allow only one of `secondary_range_blocks` or `secondary_range_names` when creating GKE clusters. ([juliocc](https://github.com/juliocc))
+- [[#1675](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1675)] GKE Autopilot module: add network tags ([olliefr](https://github.com/olliefr))
+- [[#1676](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1676)] fixed up nit from PR 1666 ([dgulli](https://github.com/dgulli))
+- [[#1672](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1672)] Added possibility to use gcs push endpoint on pubsub subscription ([luigi-bitonti](https://github.com/luigi-bitonti))
+- [[#1671](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1671)] **incompatible change:** Fixed, added back environments to each instance, that way we can also… ([apichick](https://github.com/apichick))
+- [[#1666](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1666)] added support for global proxy only subnets ([dgulli](https://github.com/dgulli))
+- [[#1669](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1669)] Fix for partner interconnect ([apichick](https://github.com/apichick))
+- [[#1668](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1668)] fix(compute-mig): add correct type optionality for metrics in autosca… ([NotArpit](https://github.com/NotArpit))
+- [[#1667](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1667)] fix(compute-mig): add mode property to compute_region_autoscaler ([NotArpit](https://github.com/NotArpit))
+- [[#1658](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1658)] **incompatible change:** Change type of `iam_bindings` variable to allow multiple conditional bindings ([ludoo](https://github.com/ludoo))
+- [[#1653](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1653)] Fixes to the apigee module ([juliocc](https://github.com/juliocc))
+- [[#1642](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1642)] New phpIPAM serverless third parties solution in blueprints ([simonebruzzechesse](https://github.com/simonebruzzechesse))
+- [[#1650](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1650)] Make net-vpc variables non-nullable ([juliocc](https://github.com/juliocc))
+- [[#1647](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1647)] Bump provider version to 4.80.0 ([juliocc](https://github.com/juliocc))
+- [[#1646](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1646)] gke-cluster-autopilot: add monitoring configuration ([olliefr](https://github.com/olliefr))
+- [[#1645](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1645)] gke-cluster-autopilot: add validation for release_channel input variable ([olliefr](https://github.com/olliefr))
+- [[#1638](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1638)] gke-cluster-standard: change logging configuration ([olliefr](https://github.com/olliefr))
+- [[#1625](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1625)] gke-cluster-autopilot: add logging configuration ([olliefr](https://github.com/olliefr))
+- [[#1637](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1637)] GRPC variable is misnamed "GRCP" in `modules/cloud-run/variables.tf`, causing liveness probe and startup probe to fail ([zacharysmithdatatonic](https://github.com/zacharysmithdatatonic))
+- [[#1632](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1632)] Vpc sc allow null for identity type ([LudovicEmo](https://github.com/LudovicEmo))
+- [[#1633](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1633)] Do not set default ASN number ([LucaPrete](https://github.com/LucaPrete))
+- [[#1631](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1631)] Allow single hfw policy association in folder and organization modules ([juliocc](https://github.com/juliocc))
+- [[#1630](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1630)] [Fix] Add explicit dependency between CR peers and NCC RA spoke creation ([LucaPrete](https://github.com/LucaPrete))
+- [[#1613](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1613)] Cloud SQL activation policy selectable ([cmvalla](https://github.com/cmvalla))
+- [[#1619](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1619)] Adding support for NAT in Apigee ([billabongrob](https://github.com/billabongrob))
+- [[#1620](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1620)] Remove net-firewall-policy match variable validation ([richard-olson](https://github.com/richard-olson))
+- [[#1614](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1614)] Fix net-firewall-policy factory name and action ([richard-olson](https://github.com/richard-olson))
+- [[#1584](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1584)] add support for object upload to gcs module ([ehorning](https://github.com/ehorning))
+- [[#1609](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1609)] **incompatible change:** Use cloud run bindings for cf v2 invoker role, refactor iam handling in cf v2 and cloud run ([ludoo](https://github.com/ludoo))
+- [[#1590](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1590)] GCVE module first release ([eliamaldini](https://github.com/eliamaldini))
- [[#1595](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1595)] **incompatible change:** IAM interface refactor ([ludoo](https://github.com/ludoo))
- [[#1600](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1600)] fix(cloud-run): move cpu boost annotation to revision ([LiuVII](https://github.com/LiuVII))
- [[#1599](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1599)] Fixing some typos ([bluPhy](https://github.com/bluPhy))
@@ -38,6 +115,9 @@ All notable changes to this project will be documented in this file.
### TOOLS
+- [[#1641](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1641)] Lint script ([juliocc](https://github.com/juliocc))
+- [[#1640](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1640)] Simplify linting output in workflow ([juliocc](https://github.com/juliocc))
+- [[#1635](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1635)] Silence FAST tests warnings ([juliocc](https://github.com/juliocc))
- [[#1595](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1595)] **incompatible change:** IAM interface refactor ([ludoo](https://github.com/ludoo))
- [[#1585](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1585)] Print inventory path when a test fails ([juliocc](https://github.com/juliocc))
@@ -1483,7 +1563,8 @@ 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/v25.0.0...HEAD
+[Unreleased]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v26.0.0...HEAD
+[26.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v25.0.0...v26.0.0
[25.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v24.0.0...v25.0.0
[24.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v23.0.0...v24.0.0
[23.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v22.0.0...v23.0.0
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3d997e94..1e12acf7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -686,8 +686,8 @@ Writing `pytest` unit tests to check plan results is really easy, but since wrap
In the following sections we describe the three testing approaches we currently have:
- [Example-based tests](#testing-via-readmemd-example-blocks): this is perhaps the easiest and most common way to test either a module or a blueprint. You simply have to provide an example call to your module and a few metadata values in the module's README.md.
-- [tfvars-based tests](#testing-via-tfvars-and-yaml): allows you to test a module or blueprint by providing variables via tfvar files and an expected plan result in form of an inventory. This type of test is useful, for example, for FAST stages that don't have any examples within their READMEs.
-- [Python-based (legacy) tests](#writing-tests-in-python--legacy-approach-): in some situations you might still want to interact directly with `tftest` via Python, if that's the case, use this method to write custom Python logic to test your module in any way you see fit.
+- [tfvars-based tests](#testing-via-tfvars-and-yaml-aka-tftest-based-tests): allows you to test a module or blueprint by providing variables via tfvar files and an expected plan result in form of an inventory. This type of test is useful, for example, for FAST stages that don't have any examples within their READMEs.
+- [Python-based (legacy) tests](#writing-tests-in-python-legacy-approach): in some situations you might still want to interact directly with `tftest` via Python, if that's the case, use this method to write custom Python logic to test your module in any way you see fit.
### Testing via README.md example blocks
@@ -818,27 +818,47 @@ Example-based test are named based on the section within the README.md that cont
Here we show a few commonly used selection commands:
- Run all examples:
- - `pytest tests/examples/`
-- Run all examples for modules:
- - `pytest -k modules/ tests/examples`
+ - `pytest tests/examples`
+- Run all examples for blueprints only:
+ - `pytest -k blueprints tests/examples`
+- Run all examples for modules only:
+ - `pytest -k modules tests/examples`
- Run all examples for the `net-vpc` module:
- - `pytest -k 'net and vpc' tests/examples`
-- Run a specific example in module `net-vpc`:
- - `pytest -k 'modules and dns and private'`
- - `pytest -v 'tests/examples/test_plan.py::test_example[modules/dns:Private Zone]'`
+ - `pytest -k 'modules and net-vpc:' tests/examples`
+- Run a specific example (identified by a substring match on its name) from the `net-vpc` module:
+ - `pytest -k 'modules and net-vpc: and ipv6' tests/examples`
+- Run a specific example (identified by its full name) from the `net-vpc` module:
+ - `pytest -v 'tests/examples/test_plan.py::test_example[modules/net-vpc:IPv6:1]'`
- Run tests for all blueprints except those under the gke directory:
- - `pytest -k 'blueprints and not gke'`
+ - `pytest -k 'blueprints and not gke' tests/examples`
-Tip: you can use `pytest --collect-only` to fine tune your selection query without actually running the tests. Once you find the expression matching your desired tests, remove the `collect-only` flag.
+> [!NOTE]
+> The colon symbol (`:`) in `pytest` keyword expression `'modules and net-vpc:'` makes sure that `net-vpc` is matched but `net-vpc-firewall` or `net-vpc-peering` are not.
+
+Tip: to list all tests matched by your keyword expression (`-k ...`) without actually running them, you can use the `--collect-only` flag.
+
+The following command executes a dry run that *lists* all example-based tests for the `gke-cluster-autopilot` module:
+
+```bash
+pytest -k 'modules and gke-cluster-autopilot:' tests/examples --collect-only
+```
+
+Once you find the expression matching your desired test(s), remove the `--collect-only` flag.
+
+The next command executes an example-based test found in the *Monitoring Configuration* section of the README file for the `gke-cluster-autopilot` module. That section actually has two tests, so the `:2` part selects the second test only:
+
+```bash
+pytest -k 'modules and gke-cluster-autopilot: and monitoring and :2' tests/examples
+```
#### Generating the inventory automatically
Building an inventory file by hand is difficult. To simplify this task, the default test runner for examples prints the inventory for the full plan if it succeeds. Therefore, you can start without an inventory and then run a test to get the full plan and extract the pieces you want to build the inventory file.
-Suppose you want to generate the inventory for the last DNS example above (the one creating the recordsets from a YAML file). Assuming that example is under the "Private Zone" section in the README for the `dns`, you can run the following command to build the inventory:
+Suppose you want to generate the inventory for the last DNS example above (the one creating the recordsets from a YAML file). Assuming that example is the first code block under the "Private Zone" section in the README for the `dns` module, you can run the following command to build the inventory:
```bash
-pytest -s 'tests/examples/test_plan.py::test_example[modules/dns:Private Zone]'
+pytest -s 'tests/examples/test_plan.py::test_example[modules/dns:Private Zone:1]'
```
which will generate a output similar to this:
diff --git a/blueprints/apigee/bigquery-analytics/README.md b/blueprints/apigee/bigquery-analytics/README.md
index 5261f72e..3eeeaaf7 100644
--- a/blueprints/apigee/bigquery-analytics/README.md
+++ b/blueprints/apigee/bigquery-analytics/README.md
@@ -53,14 +53,13 @@ Do the following to verify that everything works as expected.
4. At 4am (UTC) every day the Cloud Scheduler will run and will export the analytics to the BigQuery table. Double-check they are there.
-
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [envgroups](variables.tf#L24) | Environment groups (NAME => [HOSTNAMES]). | map(list(string))
| ✓ | |
-| [environments](variables.tf#L30) | Environments. | map(object({…}))
| ✓ | |
-| [instances](variables.tf#L46) | Instance. | map(object({…}))
| ✓ | |
+| [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)
| ✓ | |
| [datastore_name](variables.tf#L17) | Datastore. | string
| | "gcs"
|
@@ -74,7 +73,6 @@ Do the following to verify that everything works as expected.
| name | description | sensitive |
|---|---|:---:|
| [ip_address](outputs.tf#L17) | IP address. | |
-
## Test
@@ -92,13 +90,13 @@ module "test" {
environments = {
apis-test = {
envgroups = ["test"]
- regions = ["europe-west1"]
}
}
instances = {
europe-west1 = {
runtime_ip_cidr_range = "10.0.4.0/22"
troubleshooting_ip_cidr_range = "10.1.0.0/28"
+ environments = ["apis-test"]
}
}
psc_config = {
diff --git a/blueprints/apigee/bigquery-analytics/variables.tf b/blueprints/apigee/bigquery-analytics/variables.tf
index 53f329b0..3552d58e 100644
--- a/blueprints/apigee/bigquery-analytics/variables.tf
+++ b/blueprints/apigee/bigquery-analytics/variables.tf
@@ -38,7 +38,6 @@ variable "environments" {
}))
iam = optional(map(list(string)))
envgroups = optional(list(string))
- regions = optional(list(string))
}))
nullable = false
}
@@ -52,6 +51,7 @@ variable "instances" {
troubleshooting_ip_cidr_range = string
disk_encryption_key = optional(string)
consumer_accept_list = optional(list(string))
+ environments = optional(list(string))
}))
nullable = false
}
diff --git a/blueprints/apigee/bigquery-analytics/versions.tf b/blueprints/apigee/bigquery-analytics/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/apigee/bigquery-analytics/versions.tf
+++ b/blueprints/apigee/bigquery-analytics/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/apigee/hybrid-gke/gke.tf b/blueprints/apigee/hybrid-gke/gke.tf
index 6ae38433..701384b9 100644
--- a/blueprints/apigee/hybrid-gke/gke.tf
+++ b/blueprints/apigee/hybrid-gke/gke.tf
@@ -20,12 +20,9 @@ module "cluster" {
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"
- }
+ network = module.vpc.self_link
+ subnetwork = module.vpc.subnet_self_links["${var.region}/subnet-apigee"]
+ secondary_range_names = {}
master_authorized_ranges = var.cluster_network_config.master_authorized_cidr_blocks
master_ipv4_cidr_block = var.cluster_network_config.master_cidr_block
}
@@ -79,4 +76,4 @@ module "apigee-runtime-nodepool" {
create = true
}
tags = ["node"]
-}
\ No newline at end of file
+}
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
index 2923f1f6..afad0f0d 100644
--- 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
@@ -76,11 +76,11 @@ module "apigee" {
environments = {
(local.environment) = {
envgroups = [local.envgroup]
- regions = [var.region]
}
}
instances = {
(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
}
diff --git a/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/versions.tf b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/versions.tf
+++ b/blueprints/apigee/network-patterns/nb-glb-psc-neg-sb-psc-ilbl7-hybrid-neg/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/cloud-operations/adfs/versions.tf b/blueprints/cloud-operations/adfs/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/cloud-operations/adfs/versions.tf
+++ b/blueprints/cloud-operations/adfs/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/cloud-operations/asset-inventory-feed-remediation/main.tf b/blueprints/cloud-operations/asset-inventory-feed-remediation/main.tf
index e4082f69..e396364e 100644
--- a/blueprints/cloud-operations/asset-inventory-feed-remediation/main.tf
+++ b/blueprints/cloud-operations/asset-inventory-feed-remediation/main.tf
@@ -55,10 +55,12 @@ module "vpc" {
}
module "pubsub" {
- source = "../../../modules/pubsub"
- project_id = module.project.project_id
- name = var.name
- subscriptions = { "${var.name}-default" = null }
+ source = "../../../modules/pubsub"
+ project_id = module.project.project_id
+ name = var.name
+ subscriptions = {
+ "${var.name}-default" = {}
+ }
iam = {
"roles/pubsub.publisher" = [
"serviceAccount:${module.project.service_accounts.robots.cloudasset}"
diff --git a/blueprints/cloud-operations/asset-inventory-feed-remediation/versions.tf b/blueprints/cloud-operations/asset-inventory-feed-remediation/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/cloud-operations/asset-inventory-feed-remediation/versions.tf
+++ b/blueprints/cloud-operations/asset-inventory-feed-remediation/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/cloud-operations/dns-fine-grained-iam/versions.tf b/blueprints/cloud-operations/dns-fine-grained-iam/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/cloud-operations/dns-fine-grained-iam/versions.tf
+++ b/blueprints/cloud-operations/dns-fine-grained-iam/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/cloud-operations/dns-shared-vpc/versions.tf b/blueprints/cloud-operations/dns-shared-vpc/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/cloud-operations/dns-shared-vpc/versions.tf
+++ b/blueprints/cloud-operations/dns-shared-vpc/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/cloud-operations/iam-delegated-role-grants/versions.tf b/blueprints/cloud-operations/iam-delegated-role-grants/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/cloud-operations/iam-delegated-role-grants/versions.tf
+++ b/blueprints/cloud-operations/iam-delegated-role-grants/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/cloud-operations/onprem-sa-key-management/versions.tf b/blueprints/cloud-operations/onprem-sa-key-management/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/cloud-operations/onprem-sa-key-management/versions.tf
+++ b/blueprints/cloud-operations/onprem-sa-key-management/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/cloud-operations/packer-image-builder/versions.tf b/blueprints/cloud-operations/packer-image-builder/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/cloud-operations/packer-image-builder/versions.tf
+++ b/blueprints/cloud-operations/packer-image-builder/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/cloud-operations/quota-monitoring/main.tf b/blueprints/cloud-operations/quota-monitoring/main.tf
index f644c8fb..a49891c0 100644
--- a/blueprints/cloud-operations/quota-monitoring/main.tf
+++ b/blueprints/cloud-operations/quota-monitoring/main.tf
@@ -39,7 +39,7 @@ module "pubsub" {
project_id = module.project.project_id
name = var.name
subscriptions = {
- "${var.name}-default" = null
+ "${var.name}-default" = {}
}
# the Cloud Scheduler robot service account already has pubsub.topics.publish
# at the project level via roles/cloudscheduler.serviceAgent
diff --git a/blueprints/cloud-operations/quota-monitoring/versions.tf b/blueprints/cloud-operations/quota-monitoring/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/cloud-operations/quota-monitoring/versions.tf
+++ b/blueprints/cloud-operations/quota-monitoring/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/main.tf b/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/main.tf
index c10c0b6b..6460384e 100644
--- a/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/main.tf
+++ b/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/main.tf
@@ -63,7 +63,7 @@ module "pubsub" {
project_id = module.project.project_id
name = var.name
subscriptions = {
- "${var.name}-default" = null
+ "${var.name}-default" = {}
}
# the Cloud Scheduler robot service account already has pubsub.topics.publish
# at the project level via roles/cloudscheduler.serviceAgent
@@ -74,7 +74,7 @@ module "pubsub_file" {
project_id = module.project.project_id
name = var.name_cffile
subscriptions = {
- "${var.name_cffile}-default" = null
+ "${var.name_cffile}-default" = {}
}
# the Cloud Scheduler robot service account already has pubsub.topics.publish
# at the project level via roles/cloudscheduler.serviceAgent
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 e4f7404f..91a91a31 100644
--- a/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf
+++ b/blueprints/cloud-operations/scheduled-asset-inventory-export-bq/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/data-solutions/bq-ml/versions.tf b/blueprints/data-solutions/bq-ml/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/data-solutions/bq-ml/versions.tf
+++ b/blueprints/data-solutions/bq-ml/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/data-solutions/cloudsql-multiregion/README.md b/blueprints/data-solutions/cloudsql-multiregion/README.md
index 85f2594c..def4d3f1 100644
--- a/blueprints/data-solutions/cloudsql-multiregion/README.md
+++ b/blueprints/data-solutions/cloudsql-multiregion/README.md
@@ -179,5 +179,5 @@ module "test" {
}
prefix = "prefix"
}
-# tftest modules=9 resources=43
+# tftest modules=9 resources=44
```
diff --git a/blueprints/data-solutions/cmek-via-centralized-kms/main.tf b/blueprints/data-solutions/cmek-via-centralized-kms/main.tf
index 27fbe99b..fb446e71 100644
--- a/blueprints/data-solutions/cmek-via-centralized-kms/main.tf
+++ b/blueprints/data-solutions/cmek-via-centralized-kms/main.tf
@@ -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.
@@ -106,7 +106,10 @@ module "kms" {
name = "${var.prefix}-${var.region}",
location = var.region
}
- keys = { key-gce = null, key-gcs = null }
+ keys = {
+ key-gce = {}
+ key-gcs = {}
+ }
}
###############################################################################
diff --git a/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf b/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf
+++ b/blueprints/data-solutions/cmek-via-centralized-kms/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/data-solutions/composer-2/README.md b/blueprints/data-solutions/composer-2/README.md
index c43590e7..6cf927b7 100644
--- a/blueprints/data-solutions/composer-2/README.md
+++ b/blueprints/data-solutions/composer-2/README.md
@@ -139,5 +139,5 @@ module "test" {
}
prefix = "prefix"
}
-# tftest modules=5 resources=28
+# tftest modules=5 resources=29
```
diff --git a/blueprints/data-solutions/data-platform-foundations/03-composer.tf b/blueprints/data-solutions/data-platform-foundations/03-composer.tf
index f806f0e5..8c803e4b 100644
--- a/blueprints/data-solutions/data-platform-foundations/03-composer.tf
+++ b/blueprints/data-solutions/data-platform-foundations/03-composer.tf
@@ -15,7 +15,7 @@
# tfdoc:file:description Orchestration Cloud Composer definition.
locals {
- env_variables = {
+ _env_variables = {
BQ_LOCATION = var.location
DATA_CAT_TAGS = try(jsonencode(module.common-datacatalog.tags), "{}")
DF_KMS_KEY = try(var.service_encryption_keys.dataflow, "")
@@ -48,6 +48,12 @@ locals {
TRF_SA_DF = module.transf-sa-df-0.email
TRF_SA_BQ = module.transf-sa-bq-0.email
}
+ env_variables = {
+ for k, v in merge(
+ try(var.composer_config.software_config.env_variables, null),
+ local._env_variables
+ ) : "AIRFLOW_VAR_${k}" => v
+ }
}
module "orch-sa-cmp-0" {
source = "../../../modules/iam-service-account"
@@ -70,7 +76,7 @@ resource "google_composer_environment" "orch-cmp-0" {
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)
+ env_variables = local.env_variables
image_version = try(var.composer_config.software_config.image_version, null)
}
dynamic "workloads_config" {
diff --git a/blueprints/data-solutions/data-platform-foundations/demo/datapipeline.py b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline.py
index a682d346..45b71b30 100644
--- a/blueprints/data-solutions/data-platform-foundations/demo/datapipeline.py
+++ b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline.py
@@ -16,57 +16,52 @@
# Load The Dependencies
# --------------------------------------------------------------------------------
-import csv
import datetime
-import io
-import json
-import logging
-import os
from airflow import models
+from airflow.models.variable import Variable
from airflow.providers.google.cloud.operators.dataflow import DataflowTemplatedJobStartOperator
-from airflow.operators import dummy
-from airflow.providers.google.cloud.operators.bigquery import BigQueryInsertJobOperator, BigQueryUpsertTableOperator, BigQueryUpdateTableSchemaOperator
-from airflow.utils.task_group import TaskGroup
+from airflow.operators import empty
+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")
-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"
+BQ_LOCATION = Variable.get("BQ_LOCATION")
+DATA_CAT_TAGS = Variable.get("DATA_CAT_TAGS", deserialize_json=True)
+DWH_LAND_PRJ = Variable.get("DWH_LAND_PRJ")
+DWH_LAND_BQ_DATASET = Variable.get("DWH_LAND_BQ_DATASET")
+DWH_LAND_GCS = Variable.get("DWH_LAND_GCS")
+DWH_CURATED_PRJ = Variable.get("DWH_CURATED_PRJ")
+DWH_CURATED_BQ_DATASET = Variable.get("DWH_CURATED_BQ_DATASET")
+DWH_CURATED_GCS = Variable.get("DWH_CURATED_GCS")
+DWH_CONFIDENTIAL_PRJ = Variable.get("DWH_CONFIDENTIAL_PRJ")
+DWH_CONFIDENTIAL_BQ_DATASET = Variable.get("DWH_CONFIDENTIAL_BQ_DATASET")
+DWH_CONFIDENTIAL_GCS = Variable.get("DWH_CONFIDENTIAL_GCS")
+DWH_PLG_PRJ = Variable.get("DWH_PLG_PRJ")
+DWH_PLG_BQ_DATASET = Variable.get("DWH_PLG_BQ_DATASET")
+DWH_PLG_GCS = Variable.get("DWH_PLG_GCS")
+GCP_REGION = Variable.get("GCP_REGION")
+DRP_PRJ = Variable.get("DRP_PRJ")
+DRP_BQ = Variable.get("DRP_BQ")
+DRP_GCS = Variable.get("DRP_GCS")
+DRP_PS = Variable.get("DRP_PS")
+LOD_PRJ = Variable.get("LOD_PRJ")
+LOD_GCS_STAGING = Variable.get("LOD_GCS_STAGING")
+LOD_NET_VPC = Variable.get("LOD_NET_VPC")
+LOD_NET_SUBNET = Variable.get("LOD_NET_SUBNET")
+LOD_SA_DF = Variable.get("LOD_SA_DF")
+ORC_PRJ = Variable.get("ORC_PRJ")
+ORC_GCS = Variable.get("ORC_GCS")
+TRF_PRJ = Variable.get("TRF_PRJ")
+TRF_GCS_STAGING = Variable.get("TRF_GCS_STAGING")
+TRF_NET_VPC = Variable.get("TRF_NET_VPC")
+TRF_NET_SUBNET = Variable.get("TRF_NET_SUBNET")
+TRF_SA_DF = Variable.get("TRF_SA_DF")
+TRF_SA_BQ = Variable.get("TRF_SA_BQ")
+DF_KMS_KEY = Variable.get("DF_KMS_KEY", "")
+DF_REGION = Variable.get("GCP_REGION")
+DF_ZONE = Variable.get("GCP_REGION") + "-b"
# --------------------------------------------------------------------------------
# Set default arguments
@@ -106,12 +101,12 @@ with models.DAG(
'data_pipeline_dag',
default_args=default_args,
schedule_interval=None) as dag:
- start = dummy.DummyOperator(
+ start = empty.EmptyOperator(
task_id='start',
trigger_rule='all_success'
)
- end = dummy.DummyOperator(
+ end = empty.EmptyOperator(
task_id='end',
trigger_rule='all_success'
)
diff --git a/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_dc_tags.py b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_dc_tags.py
index 56e62897..5e86472a 100644
--- a/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_dc_tags.py
+++ b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_dc_tags.py
@@ -16,57 +16,53 @@
# Load The Dependencies
# --------------------------------------------------------------------------------
-import csv
import datetime
-import io
-import json
-import logging
-import os
from airflow import models
+from airflow.models.variable import Variable
from airflow.providers.google.cloud.operators.dataflow import DataflowTemplatedJobStartOperator
-from airflow.operators import dummy
+from airflow.operators import empty
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")
-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"
+BQ_LOCATION = Variable.get("BQ_LOCATION")
+DATA_CAT_TAGS = Variable.get("DATA_CAT_TAGS", deserialize_json=True)
+DWH_LAND_PRJ = Variable.get("DWH_LAND_PRJ")
+DWH_LAND_BQ_DATASET = Variable.get("DWH_LAND_BQ_DATASET")
+DWH_LAND_GCS = Variable.get("DWH_LAND_GCS")
+DWH_CURATED_PRJ = Variable.get("DWH_CURATED_PRJ")
+DWH_CURATED_BQ_DATASET = Variable.get("DWH_CURATED_BQ_DATASET")
+DWH_CURATED_GCS = Variable.get("DWH_CURATED_GCS")
+DWH_CONFIDENTIAL_PRJ = Variable.get("DWH_CONFIDENTIAL_PRJ")
+DWH_CONFIDENTIAL_BQ_DATASET = Variable.get("DWH_CONFIDENTIAL_BQ_DATASET")
+DWH_CONFIDENTIAL_GCS = Variable.get("DWH_CONFIDENTIAL_GCS")
+DWH_PLG_PRJ = Variable.get("DWH_PLG_PRJ")
+DWH_PLG_BQ_DATASET = Variable.get("DWH_PLG_BQ_DATASET")
+DWH_PLG_GCS = Variable.get("DWH_PLG_GCS")
+GCP_REGION = Variable.get("GCP_REGION")
+DRP_PRJ = Variable.get("DRP_PRJ")
+DRP_BQ = Variable.get("DRP_BQ")
+DRP_GCS = Variable.get("DRP_GCS")
+DRP_PS = Variable.get("DRP_PS")
+LOD_PRJ = Variable.get("LOD_PRJ")
+LOD_GCS_STAGING = Variable.get("LOD_GCS_STAGING")
+LOD_NET_VPC = Variable.get("LOD_NET_VPC")
+LOD_NET_SUBNET = Variable.get("LOD_NET_SUBNET")
+LOD_SA_DF = Variable.get("LOD_SA_DF")
+ORC_PRJ = Variable.get("ORC_PRJ")
+ORC_GCS = Variable.get("ORC_GCS")
+TRF_PRJ = Variable.get("TRF_PRJ")
+TRF_GCS_STAGING = Variable.get("TRF_GCS_STAGING")
+TRF_NET_VPC = Variable.get("TRF_NET_VPC")
+TRF_NET_SUBNET = Variable.get("TRF_NET_SUBNET")
+TRF_SA_DF = Variable.get("TRF_SA_DF")
+TRF_SA_BQ = Variable.get("TRF_SA_BQ")
+DF_KMS_KEY = Variable.get("DF_KMS_KEY", "")
+DF_REGION = Variable.get("GCP_REGION")
+DF_ZONE = Variable.get("GCP_REGION") + "-b"
# --------------------------------------------------------------------------------
# Set default arguments
@@ -106,12 +102,12 @@ with models.DAG(
'data_pipeline_dc_tags_dag',
default_args=default_args,
schedule_interval=None) as dag:
- start = dummy.DummyOperator(
+ start = empty.EmptyOperator(
task_id='start',
trigger_rule='all_success'
)
- end = dummy.DummyOperator(
+ end = empty.EmptyOperator(
task_id='end',
trigger_rule='all_success'
)
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
index b6784b9e..7bbf67a1 100644
--- 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
@@ -17,12 +17,11 @@
# --------------------------------------------------------------------------------
import datetime
-import json
-import os
import time
from airflow import models
-from airflow.operators import dummy
+from airflow.models.variable import Variable
+from airflow.operators import empty
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
@@ -30,42 +29,42 @@ 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"
+BQ_LOCATION = Variable.get("BQ_LOCATION")
+DATA_CAT_TAGS = Variable.get("DATA_CAT_TAGS", deserialize_json=True)
+DWH_LAND_PRJ = Variable.get("DWH_LAND_PRJ")
+DWH_LAND_BQ_DATASET = Variable.get("DWH_LAND_BQ_DATASET")
+DWH_LAND_GCS = Variable.get("DWH_LAND_GCS")
+DWH_CURATED_PRJ = Variable.get("DWH_CURATED_PRJ")
+DWH_CURATED_BQ_DATASET = Variable.get("DWH_CURATED_BQ_DATASET")
+DWH_CURATED_GCS = Variable.get("DWH_CURATED_GCS")
+DWH_CONFIDENTIAL_PRJ = Variable.get("DWH_CONFIDENTIAL_PRJ")
+DWH_CONFIDENTIAL_BQ_DATASET = Variable.get("DWH_CONFIDENTIAL_BQ_DATASET")
+DWH_CONFIDENTIAL_GCS = Variable.get("DWH_CONFIDENTIAL_GCS")
+DWH_PLG_PRJ = Variable.get("DWH_PLG_PRJ")
+DWH_PLG_BQ_DATASET = Variable.get("DWH_PLG_BQ_DATASET")
+DWH_PLG_GCS = Variable.get("DWH_PLG_GCS")
+GCP_REGION = Variable.get("GCP_REGION")
+DRP_PRJ = Variable.get("DRP_PRJ")
+DRP_BQ = Variable.get("DRP_BQ")
+DRP_GCS = Variable.get("DRP_GCS")
+DRP_PS = Variable.get("DRP_PS")
+LOD_PRJ = Variable.get("LOD_PRJ")
+LOD_GCS_STAGING = Variable.get("LOD_GCS_STAGING")
+LOD_NET_VPC = Variable.get("LOD_NET_VPC")
+LOD_NET_SUBNET = Variable.get("LOD_NET_SUBNET")
+LOD_SA_DF = Variable.get("LOD_SA_DF")
+ORC_PRJ = Variable.get("ORC_PRJ")
+ORC_GCS = Variable.get("ORC_GCS")
+ORC_GCS_TMP_DF = Variable.get("ORC_GCS_TMP_DF")
+TRF_PRJ = Variable.get("TRF_PRJ")
+TRF_GCS_STAGING = Variable.get("TRF_GCS_STAGING")
+TRF_NET_VPC = Variable.get("TRF_NET_VPC")
+TRF_NET_SUBNET = Variable.get("TRF_NET_SUBNET")
+TRF_SA_DF = Variable.get("TRF_SA_DF")
+TRF_SA_BQ = Variable.get("TRF_SA_BQ")
+DF_KMS_KEY = Variable.get("DF_KMS_KEY", "")
+DF_REGION = Variable.get("GCP_REGION")
+DF_ZONE = Variable.get("GCP_REGION") + "-b"
# --------------------------------------------------------------------------------
# Set default arguments
@@ -104,9 +103,9 @@ dataflow_environment = {
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')
+ start = empty.EmptyOperator(task_id='start', trigger_rule='all_success')
- end = dummy.DummyOperator(task_id='end', trigger_rule='all_success')
+ end = empty.EmptyOperator(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.
diff --git a/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_flex.py b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_flex.py
index 34ff10cc..5e60c62f 100644
--- a/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_flex.py
+++ b/blueprints/data-solutions/data-platform-foundations/demo/datapipeline_flex.py
@@ -17,54 +17,53 @@
# --------------------------------------------------------------------------------
import datetime
-import json
-import os
import time
from airflow import models
+from airflow.models.variable import Variable
from airflow.providers.google.cloud.operators.dataflow import DataflowStartFlexTemplateOperator
-from airflow.operators import dummy
+from airflow.operators import empty
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"
+BQ_LOCATION = Variable.get("BQ_LOCATION")
+DATA_CAT_TAGS = Variable.get("DATA_CAT_TAGS", deserialize_json=True)
+DWH_LAND_PRJ = Variable.get("DWH_LAND_PRJ")
+DWH_LAND_BQ_DATASET = Variable.get("DWH_LAND_BQ_DATASET")
+DWH_LAND_GCS = Variable.get("DWH_LAND_GCS")
+DWH_CURATED_PRJ = Variable.get("DWH_CURATED_PRJ")
+DWH_CURATED_BQ_DATASET = Variable.get("DWH_CURATED_BQ_DATASET")
+DWH_CURATED_GCS = Variable.get("DWH_CURATED_GCS")
+DWH_CONFIDENTIAL_PRJ = Variable.get("DWH_CONFIDENTIAL_PRJ")
+DWH_CONFIDENTIAL_BQ_DATASET = Variable.get("DWH_CONFIDENTIAL_BQ_DATASET")
+DWH_CONFIDENTIAL_GCS = Variable.get("DWH_CONFIDENTIAL_GCS")
+DWH_PLG_PRJ = Variable.get("DWH_PLG_PRJ")
+DWH_PLG_BQ_DATASET = Variable.get("DWH_PLG_BQ_DATASET")
+DWH_PLG_GCS = Variable.get("DWH_PLG_GCS")
+GCP_REGION = Variable.get("GCP_REGION")
+DRP_PRJ = Variable.get("DRP_PRJ")
+DRP_BQ = Variable.get("DRP_BQ")
+DRP_GCS = Variable.get("DRP_GCS")
+DRP_PS = Variable.get("DRP_PS")
+LOD_PRJ = Variable.get("LOD_PRJ")
+LOD_GCS_STAGING = Variable.get("LOD_GCS_STAGING")
+LOD_NET_VPC = Variable.get("LOD_NET_VPC")
+LOD_NET_SUBNET = Variable.get("LOD_NET_SUBNET")
+LOD_SA_DF = Variable.get("LOD_SA_DF")
+ORC_PRJ = Variable.get("ORC_PRJ")
+ORC_GCS = Variable.get("ORC_GCS")
+ORC_GCS_TMP_DF = Variable.get("ORC_GCS_TMP_DF")
+TRF_PRJ = Variable.get("TRF_PRJ")
+TRF_GCS_STAGING = Variable.get("TRF_GCS_STAGING")
+TRF_NET_VPC = Variable.get("TRF_NET_VPC")
+TRF_NET_SUBNET = Variable.get("TRF_NET_SUBNET")
+TRF_SA_DF = Variable.get("TRF_SA_DF")
+TRF_SA_BQ = Variable.get("TRF_SA_BQ")
+DF_KMS_KEY = Variable.get("DF_KMS_KEY", "")
+DF_REGION = Variable.get("GCP_REGION")
+DF_ZONE = Variable.get("GCP_REGION") + "-b"
# --------------------------------------------------------------------------------
# Set default arguments
@@ -104,9 +103,9 @@ 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')
+ start = empty.EmptyOperator(task_id='start', trigger_rule='all_success')
- end = dummy.DummyOperator(task_id='end', trigger_rule='all_success')
+ end = empty.EmptyOperator(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.
diff --git a/blueprints/data-solutions/data-platform-foundations/demo/delete_table.py b/blueprints/data-solutions/data-platform-foundations/demo/delete_table.py
index bade0388..252400ad 100644
--- a/blueprints/data-solutions/data-platform-foundations/demo/delete_table.py
+++ b/blueprints/data-solutions/data-platform-foundations/demo/delete_table.py
@@ -24,49 +24,49 @@ import logging
import os
from airflow import models
-from airflow.providers.google.cloud.operators.dataflow import DataflowTemplatedJobStartOperator
-from airflow.operators import dummy
+from airflow.models.variable import Variable
+from airflow.operators import empty
from airflow.providers.google.cloud.operators.bigquery import BigQueryDeleteTableOperator
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")
-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"
+BQ_LOCATION = Variable.get("BQ_LOCATION")
+DATA_CAT_TAGS = Variable.get("DATA_CAT_TAGS", deserialize_json=True)
+DWH_LAND_PRJ = Variable.get("DWH_LAND_PRJ")
+DWH_LAND_BQ_DATASET = Variable.get("DWH_LAND_BQ_DATASET")
+DWH_LAND_GCS = Variable.get("DWH_LAND_GCS")
+DWH_CURATED_PRJ = Variable.get("DWH_CURATED_PRJ")
+DWH_CURATED_BQ_DATASET = Variable.get("DWH_CURATED_BQ_DATASET")
+DWH_CURATED_GCS = Variable.get("DWH_CURATED_GCS")
+DWH_CONFIDENTIAL_PRJ = Variable.get("DWH_CONFIDENTIAL_PRJ")
+DWH_CONFIDENTIAL_BQ_DATASET = Variable.get("DWH_CONFIDENTIAL_BQ_DATASET")
+DWH_CONFIDENTIAL_GCS = Variable.get("DWH_CONFIDENTIAL_GCS")
+DWH_PLG_PRJ = Variable.get("DWH_PLG_PRJ")
+DWH_PLG_BQ_DATASET = Variable.get("DWH_PLG_BQ_DATASET")
+DWH_PLG_GCS = Variable.get("DWH_PLG_GCS")
+GCP_REGION = Variable.get("GCP_REGION")
+DRP_PRJ = Variable.get("DRP_PRJ")
+DRP_BQ = Variable.get("DRP_BQ")
+DRP_GCS = Variable.get("DRP_GCS")
+DRP_PS = Variable.get("DRP_PS")
+LOD_PRJ = Variable.get("LOD_PRJ")
+LOD_GCS_STAGING = Variable.get("LOD_GCS_STAGING")
+LOD_NET_VPC = Variable.get("LOD_NET_VPC")
+LOD_NET_SUBNET = Variable.get("LOD_NET_SUBNET")
+LOD_SA_DF = Variable.get("LOD_SA_DF")
+ORC_PRJ = Variable.get("ORC_PRJ")
+ORC_GCS = Variable.get("ORC_GCS")
+TRF_PRJ = Variable.get("TRF_PRJ")
+TRF_GCS_STAGING = Variable.get("TRF_GCS_STAGING")
+TRF_NET_VPC = Variable.get("TRF_NET_VPC")
+TRF_NET_SUBNET = Variable.get("TRF_NET_SUBNET")
+TRF_SA_DF = Variable.get("TRF_SA_DF")
+TRF_SA_BQ = Variable.get("TRF_SA_BQ")
+DF_KMS_KEY = Variable.get("DF_KMS_KEY", "")
+DF_REGION = Variable.get("GCP_REGION")
+DF_ZONE = Variable.get("GCP_REGION") + "-b"
# --------------------------------------------------------------------------------
# Set default arguments
@@ -106,19 +106,19 @@ with models.DAG(
'delete_tables_dag',
default_args=default_args,
schedule_interval=None) as dag:
- start = dummy.DummyOperator(
+ start = empty.EmptyOperator(
task_id='start',
trigger_rule='all_success'
)
- end = dummy.DummyOperator(
+ end = empty.EmptyOperator(
task_id='end',
trigger_rule='all_success'
)
# Bigquery Tables deleted here for demo porpuse.
# Consider a dedicated pipeline or tool for a real life scenario.
- with TaskGroup('delete_table') as delte_table:
+ with TaskGroup('delete_table') as delete_table:
delete_table_customers = BigQueryDeleteTableOperator(
task_id="delete_table_customers",
deletion_dataset_table=DWH_LAND_PRJ+"."+DWH_LAND_BQ_DATASET+".customers",
@@ -143,4 +143,4 @@ with models.DAG(
impersonation_chain=[TRF_SA_DF]
)
- start >> delte_table >> end
+ start >> delete_table >> end
diff --git a/blueprints/data-solutions/data-platform-minimal/01-landing.tf b/blueprints/data-solutions/data-platform-minimal/01-landing.tf
index 94ecf5a3..52bf6e8a 100644
--- a/blueprints/data-solutions/data-platform-minimal/01-landing.tf
+++ b/blueprints/data-solutions/data-platform-minimal/01-landing.tf
@@ -64,6 +64,7 @@ module "land-project" {
"bigquerystorage.googleapis.com",
"cloudkms.googleapis.com",
"cloudresourcemanager.googleapis.com",
+ "datalineage.googleapis.com",
"iam.googleapis.com",
"serviceusage.googleapis.com",
"stackdriver.googleapis.com",
diff --git a/blueprints/data-solutions/data-platform-minimal/02-composer.tf b/blueprints/data-solutions/data-platform-minimal/02-composer.tf
index da6fca9a..c250b1fd 100644
--- a/blueprints/data-solutions/data-platform-minimal/02-composer.tf
+++ b/blueprints/data-solutions/data-platform-minimal/02-composer.tf
@@ -15,7 +15,7 @@
# tfdoc:file:description Cloud Composer resources.
locals {
- env_variables = {
+ _env_variables = {
BQ_LOCATION = var.location
CURATED_BQ_DATASET = module.cur-bq-0.dataset_id
CURATED_GCS = module.cur-cs-0.url
@@ -31,6 +31,11 @@ locals {
PROCESSING_SUBNET = local.processing_subnet
PROCESSING_VPC = local.processing_vpc
}
+ env_variables = {
+ for k, v in merge(
+ var.composer_config.software_config.env_variables, local._env_variables
+ ) : "AIRFLOW_VAR_${k}" => v
+ }
}
module "processing-sa-cmp-0" {
@@ -46,18 +51,20 @@ module "processing-sa-cmp-0" {
}
resource "google_composer_environment" "processing-cmp-0" {
- count = var.enable_services.composer == true ? 1 : 0
- project = module.processing-project.project_id
- name = "${var.prefix}-prc-cmp-0"
- region = var.region
+ count = var.enable_services.composer == true ? 1 : 0
+ provider = google-beta
+ project = module.processing-project.project_id
+ name = "${var.prefix}-prc-cmp-0"
+ region = var.region
config {
software_config {
airflow_config_overrides = var.composer_config.software_config.airflow_config_overrides
pypi_packages = var.composer_config.software_config.pypi_packages
- env_variables = merge(
- var.composer_config.software_config.env_variables, local.env_variables
- )
- image_version = var.composer_config.software_config.image_version
+ env_variables = local.env_variables
+ image_version = var.composer_config.software_config.image_version
+ cloud_data_lineage_integration {
+ enabled = var.composer_config.software_config.cloud_data_lineage_integration
+ }
}
workloads_config {
scheduler {
diff --git a/blueprints/data-solutions/data-platform-minimal/02-processing.tf b/blueprints/data-solutions/data-platform-minimal/02-processing.tf
index 1bba98da..720e2a81 100644
--- a/blueprints/data-solutions/data-platform-minimal/02-processing.tf
+++ b/blueprints/data-solutions/data-platform-minimal/02-processing.tf
@@ -118,6 +118,7 @@ module "processing-project" {
"compute.googleapis.com",
"container.googleapis.com",
"dataflow.googleapis.com",
+ "datalineage.googleapis.com",
"dataproc.googleapis.com",
"iam.googleapis.com",
"servicenetworking.googleapis.com",
diff --git a/blueprints/data-solutions/data-platform-minimal/03-curated.tf b/blueprints/data-solutions/data-platform-minimal/03-curated.tf
index 8bff815f..53a6e7b2 100644
--- a/blueprints/data-solutions/data-platform-minimal/03-curated.tf
+++ b/blueprints/data-solutions/data-platform-minimal/03-curated.tf
@@ -22,6 +22,7 @@ locals {
"cloudkms.googleapis.com",
"cloudresourcemanager.googleapis.com",
"compute.googleapis.com",
+ "datalineage.googleapis.com",
"iam.googleapis.com",
"servicenetworking.googleapis.com",
"serviceusage.googleapis.com",
diff --git a/blueprints/data-solutions/data-platform-minimal/README.md b/blueprints/data-solutions/data-platform-minimal/README.md
index 1f4eb777..62b30acd 100644
--- a/blueprints/data-solutions/data-platform-minimal/README.md
+++ b/blueprints/data-solutions/data-platform-minimal/README.md
@@ -229,7 +229,7 @@ module "data-platform" {
prefix = "myprefix"
}
-# tftest modules=23 resources=135
+# tftest modules=23 resources=138
```
## Customizations
@@ -302,19 +302,19 @@ The application layer is out of scope of this script. As a demo purpuse only, on
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [organization_domain](variables.tf#L122) | Organization domain. | string
| ✓ | |
-| [prefix](variables.tf#L127) | Prefix used for resource names. | string
| ✓ | |
-| [project_config](variables.tf#L136) | 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({…})
| ✓ | |
-| [composer_config](variables.tf#L17) | Cloud Composer config. | object({…})
| | {}
|
-| [data_catalog_tags](variables.tf#L55) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(object({…}))
| | {…}
|
-| [data_force_destroy](variables.tf#L69) | Flag to set 'force_destroy' on data services like BiguQery or Cloud Storage. | bool
| | false
|
-| [enable_services](variables.tf#L75) | Flag to enable or disable services in the Data Platform. | object({…})
| | {}
|
-| [groups](variables.tf#L84) | User groups. | map(string)
| | {…}
|
-| [location](variables.tf#L94) | Location used for multi-regional resources. | string
| | "eu"
|
-| [network_config](variables.tf#L100) | Shared VPC network configurations to use. If null networks will be created in projects. | object({…})
| | {}
|
-| [project_suffix](variables.tf#L160) | Suffix used only for project ids. | string
| | null
|
-| [region](variables.tf#L166) | Region used for regional resources. | string
| | "europe-west1"
|
-| [service_encryption_keys](variables.tf#L172) | Cloud KMS to use to encrypt different services. Key location should match service region. | object({…})
| | {}
|
+| [organization_domain](variables.tf#L123) | Organization domain. | string
| ✓ | |
+| [prefix](variables.tf#L128) | Prefix used for resource names. | string
| ✓ | |
+| [project_config](variables.tf#L137) | 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({…})
| ✓ | |
+| [composer_config](variables.tf#L17) | Cloud Composer config. | object({…})
| | {}
|
+| [data_catalog_tags](variables.tf#L56) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(object({…}))
| | {…}
|
+| [data_force_destroy](variables.tf#L70) | Flag to set 'force_destroy' on data services like BiguQery or Cloud Storage. | bool
| | false
|
+| [enable_services](variables.tf#L76) | Flag to enable or disable services in the Data Platform. | object({…})
| | {}
|
+| [groups](variables.tf#L85) | User groups. | map(string)
| | {…}
|
+| [location](variables.tf#L95) | Location used for multi-regional resources. | string
| | "eu"
|
+| [network_config](variables.tf#L101) | Shared VPC network configurations to use. If null networks will be created in projects. | object({…})
| | {}
|
+| [project_suffix](variables.tf#L161) | Suffix used only for project ids. | string
| | null
|
+| [region](variables.tf#L167) | Region used for regional resources. | string
| | "europe-west1"
|
+| [service_encryption_keys](variables.tf#L173) | Cloud KMS to use to encrypt different services. Key location should match service region. | object({…})
| | {}
|
## Outputs
diff --git a/blueprints/data-solutions/data-platform-minimal/demo/README.md b/blueprints/data-solutions/data-platform-minimal/demo/README.md
index b9a24b82..f3c1cbf7 100644
--- a/blueprints/data-solutions/data-platform-minimal/demo/README.md
+++ b/blueprints/data-solutions/data-platform-minimal/demo/README.md
@@ -54,5 +54,5 @@ source ./env.sh
gsutil -i $LND_SA cp demo/data/*.csv gs://$LND_GCS
gsutil -i $CMP_SA cp demo/data/*.j* gs://$PRC_GCS
gsutil -i $CMP_SA cp demo/pyspark_* gs://$PRC_GCS
-gsutil -i $CMP_SA cp demo/dag_*.py $CMP_GCS
+gsutil -i $CMP_SA cp demo/dag_*.py gs://$CMP_GCS/dags
```
diff --git a/blueprints/data-solutions/data-platform-minimal/demo/dag_bq_gcs2bq.py b/blueprints/data-solutions/data-platform-minimal/demo/dag_bq_gcs2bq.py
index 7abf3691..321071b2 100644
--- a/blueprints/data-solutions/data-platform-minimal/demo/dag_bq_gcs2bq.py
+++ b/blueprints/data-solutions/data-platform-minimal/demo/dag_bq_gcs2bq.py
@@ -16,34 +16,30 @@
# Load The Dependencies
# --------------------------------------------------------------------------------
-import csv
import datetime
-import io
-import json
-import logging
-import os
from airflow import models
-from airflow.operators import dummy
+from airflow.models.variable import Variable
+from airflow.operators import empty
from airflow.providers.google.cloud.transfers.gcs_to_bigquery import GCSToBigQueryOperator
# --------------------------------------------------------------------------------
# Set variables - Needed for the DEMO
# --------------------------------------------------------------------------------
-BQ_LOCATION = os.environ.get("BQ_LOCATION")
-CURATED_PRJ = os.environ.get("CURATED_PRJ")
-CURATED_BQ_DATASET = os.environ.get("CURATED_BQ_DATASET")
-CURATED_GCS = os.environ.get("CURATED_GCS")
-LAND_PRJ = os.environ.get("LAND_PRJ")
-LAND_GCS = os.environ.get("LAND_GCS")
-PROCESSING_GCS = os.environ.get("PROCESSING_GCS")
-PROCESSING_SA = os.environ.get("PROCESSING_SA")
-PROCESSING_PRJ = os.environ.get("PROCESSING_PRJ")
-PROCESSING_SUBNET = os.environ.get("PROCESSING_SUBNET")
-PROCESSING_VPC = os.environ.get("PROCESSING_VPC")
-DP_KMS_KEY = os.environ.get("DP_KMS_KEY", "")
-DP_REGION = os.environ.get("DP_REGION")
-DP_ZONE = os.environ.get("DP_REGION") + "-b"
+BQ_LOCATION = Variable.get("BQ_LOCATION")
+CURATED_PRJ = Variable.get("CURATED_PRJ")
+CURATED_BQ_DATASET = Variable.get("CURATED_BQ_DATASET")
+CURATED_GCS = Variable.get("CURATED_GCS")
+LAND_PRJ = Variable.get("LAND_PRJ")
+LAND_GCS = Variable.get("LAND_GCS")
+PROCESSING_GCS = Variable.get("PROCESSING_GCS")
+PROCESSING_SA = Variable.get("PROCESSING_SA")
+PROCESSING_PRJ = Variable.get("PROCESSING_PRJ")
+PROCESSING_SUBNET = Variable.get("PROCESSING_SUBNET")
+PROCESSING_VPC = Variable.get("PROCESSING_VPC")
+DP_KMS_KEY = Variable.get("DP_KMS_KEY", "")
+DP_REGION = Variable.get("DP_REGION")
+DP_ZONE = Variable.get("DP_REGION") + "-b"
# --------------------------------------------------------------------------------
# Set default arguments
@@ -73,12 +69,12 @@ with models.DAG(
'bq_gcs2bq',
default_args=default_args,
schedule_interval=None) as dag:
- start = dummy.DummyOperator(
+ start = empty.EmptyOperator(
task_id='start',
trigger_rule='all_success'
)
- end = dummy.DummyOperator(
+ end = empty.EmptyOperator(
task_id='end',
trigger_rule='all_success'
)
@@ -96,7 +92,7 @@ with models.DAG(
schema_update_options=['ALLOW_FIELD_RELAXATION', 'ALLOW_FIELD_ADDITION'],
schema_object="customers.json",
schema_object_bucket=PROCESSING_GCS[5:],
- project_id=PROCESSING_PRJ, # The process will continue to run on the dataset project until the Apache Airflow bug is fixed. https://github.com/apache/airflow/issues/32106
+ project_id=PROCESSING_PRJ,
impersonation_chain=[PROCESSING_SA]
)
diff --git a/blueprints/data-solutions/data-platform-minimal/demo/dag_dataflow_gcs2bq.py b/blueprints/data-solutions/data-platform-minimal/demo/dag_dataflow_gcs2bq.py
index 6556de8f..111efcdc 100644
--- a/blueprints/data-solutions/data-platform-minimal/demo/dag_dataflow_gcs2bq.py
+++ b/blueprints/data-solutions/data-platform-minimal/demo/dag_dataflow_gcs2bq.py
@@ -16,36 +16,30 @@
# Load The Dependencies
# --------------------------------------------------------------------------------
-import csv
import datetime
-import io
-import json
-import logging
-import os
from airflow import models
+from airflow.models.variable import Variable
+from airflow.operators import empty
from airflow.providers.google.cloud.operators.dataflow import DataflowTemplatedJobStartOperator
-from airflow.operators import dummy
-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")
-CURATED_PRJ = os.environ.get("CURATED_PRJ")
-CURATED_BQ_DATASET = os.environ.get("CURATED_BQ_DATASET")
-CURATED_GCS = os.environ.get("CURATED_GCS")
-LAND_PRJ = os.environ.get("LAND_PRJ")
-LAND_GCS = os.environ.get("LAND_GCS")
-PROCESSING_GCS = os.environ.get("PROCESSING_GCS")
-PROCESSING_SA = os.environ.get("PROCESSING_SA")
-PROCESSING_PRJ = os.environ.get("PROCESSING_PRJ")
-PROCESSING_SUBNET = os.environ.get("PROCESSING_SUBNET")
-PROCESSING_VPC = os.environ.get("PROCESSING_VPC")
-DP_KMS_KEY = os.environ.get("DP_KMS_KEY", "")
-DP_REGION = os.environ.get("DP_REGION")
-DP_ZONE = os.environ.get("DP_REGION") + "-b"
+BQ_LOCATION = Variable.get("BQ_LOCATION")
+CURATED_PRJ = Variable.get("CURATED_PRJ")
+CURATED_BQ_DATASET = Variable.get("CURATED_BQ_DATASET")
+CURATED_GCS = Variable.get("CURATED_GCS")
+LAND_PRJ = Variable.get("LAND_PRJ")
+LAND_GCS = Variable.get("LAND_GCS")
+PROCESSING_GCS = Variable.get("PROCESSING_GCS")
+PROCESSING_SA = Variable.get("PROCESSING_SA")
+PROCESSING_PRJ = Variable.get("PROCESSING_PRJ")
+PROCESSING_SUBNET = Variable.get("PROCESSING_SUBNET")
+PROCESSING_VPC = Variable.get("PROCESSING_VPC")
+DP_KMS_KEY = Variable.get("DP_KMS_KEY", "")
+DP_REGION = Variable.get("DP_REGION")
+DP_ZONE = Variable.get("DP_REGION") + "-b"
# --------------------------------------------------------------------------------
# Set default arguments
@@ -85,12 +79,12 @@ with models.DAG(
'dataflow_gcs2bq',
default_args=default_args,
schedule_interval=None) as dag:
- start = dummy.DummyOperator(
+ start = empty.EmptyOperator(
task_id='start',
trigger_rule='all_success'
)
- end = dummy.DummyOperator(
+ end = empty.EmptyOperator(
task_id='end',
trigger_rule='all_success'
)
diff --git a/blueprints/data-solutions/data-platform-minimal/demo/dag_dataproc_gcs2bq.py b/blueprints/data-solutions/data-platform-minimal/demo/dag_dataproc_gcs2bq.py
index a404fa06..3a3dab52 100644
--- a/blueprints/data-solutions/data-platform-minimal/demo/dag_dataproc_gcs2bq.py
+++ b/blueprints/data-solutions/data-platform-minimal/demo/dag_dataproc_gcs2bq.py
@@ -14,14 +14,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import datetime
import time
-import os
from airflow import models
-from airflow.operators import dummy
+from airflow.models.variable import Variable
+from airflow.operators import empty
from airflow.providers.google.cloud.operators.dataproc import (
- DataprocCreateBatchOperator, DataprocDeleteBatchOperator, DataprocGetBatchOperator, DataprocListBatchesOperator
+ DataprocCreateBatchOperator
)
from airflow.utils.dates import days_ago
@@ -29,22 +28,21 @@ from airflow.utils.dates import days_ago
# --------------------------------------------------------------------------------
# Get variables
# --------------------------------------------------------------------------------
-BQ_LOCATION = os.environ.get("BQ_LOCATION")
-CURATED_BQ_DATASET = os.environ.get("CURATED_BQ_DATASET")
-CURATED_GCS = os.environ.get("CURATED_GCS")
-CURATED_PRJ = os.environ.get("CURATED_PRJ")
-DP_KMS_KEY = os.environ.get("DP_KMS_KEY", "")
-DP_REGION = os.environ.get("DP_REGION")
-GCP_REGION = os.environ.get("GCP_REGION")
-LAND_PRJ = os.environ.get("LAND_PRJ")
-LAND_BQ_DATASET = os.environ.get("LAND_BQ_DATASET")
-LAND_GCS = os.environ.get("LAND_GCS")
-PHS_CLUSTER_NAME = os.environ.get("PHS_CLUSTER_NAME")
-PROCESSING_GCS = os.environ.get("PROCESSING_GCS")
-PROCESSING_PRJ = os.environ.get("PROCESSING_PRJ")
-PROCESSING_SA = os.environ.get("PROCESSING_SA")
-PROCESSING_SUBNET = os.environ.get("PROCESSING_SUBNET")
-PROCESSING_VPC = os.environ.get("PROCESSING_VPC")
+BQ_LOCATION = Variable.get("BQ_LOCATION")
+CURATED_BQ_DATASET = Variable.get("CURATED_BQ_DATASET")
+CURATED_GCS = Variable.get("CURATED_GCS")
+CURATED_PRJ = Variable.get("CURATED_PRJ")
+DP_KMS_KEY = Variable.get("DP_KMS_KEY", "")
+DP_REGION = Variable.get("DP_REGION")
+LAND_PRJ = Variable.get("LAND_PRJ")
+LAND_BQ_DATASET = Variable.get("LAND_BQ_DATASET")
+LAND_GCS = Variable.get("LAND_GCS")
+PHS_CLUSTER_NAME = Variable.get("PHS_CLUSTER_NAME")
+PROCESSING_GCS = Variable.get("PROCESSING_GCS")
+PROCESSING_PRJ = Variable.get("PROCESSING_PRJ")
+PROCESSING_SA = Variable.get("PROCESSING_SA")
+PROCESSING_SUBNET = Variable.get("PROCESSING_SUBNET")
+PROCESSING_VPC = Variable.get("PROCESSING_VPC")
PYTHON_FILE_LOCATION = PROCESSING_GCS+"/pyspark_gcs2bq.py"
PHS_CLUSTER_PATH = "projects/"+PROCESSING_PRJ+"/regions/"+DP_REGION+"/clusters/"+PHS_CLUSTER_NAME
@@ -61,12 +59,12 @@ with models.DAG(
default_args=default_args, # The interval with which to schedule the DAG
schedule_interval=None, # Override to match your needs
) as dag:
- start = dummy.DummyOperator(
+ start = empty.EmptyOperator(
task_id='start',
trigger_rule='all_success'
)
- end = dummy.DummyOperator(
+ end = empty.EmptyOperator(
task_id='end',
trigger_rule='all_success'
)
diff --git a/blueprints/data-solutions/data-platform-minimal/demo/dag_delete_table.py b/blueprints/data-solutions/data-platform-minimal/demo/dag_delete_table.py
index c17c1381..9653cac7 100644
--- a/blueprints/data-solutions/data-platform-minimal/demo/dag_delete_table.py
+++ b/blueprints/data-solutions/data-platform-minimal/demo/dag_delete_table.py
@@ -16,36 +16,31 @@
# Load The Dependencies
# --------------------------------------------------------------------------------
-import csv
import datetime
-import io
-import json
-import logging
-import os
from airflow import models
-from airflow.providers.google.cloud.operators.dataflow import DataflowTemplatedJobStartOperator
-from airflow.operators import dummy
+from airflow.models.variable import Variable
+from airflow.operators import empty
from airflow.providers.google.cloud.operators.bigquery import BigQueryDeleteTableOperator
from airflow.utils.task_group import TaskGroup
# --------------------------------------------------------------------------------
# Set variables - Needed for the DEMO
# --------------------------------------------------------------------------------
-BQ_LOCATION = os.environ.get("BQ_LOCATION")
-CURATED_PRJ = os.environ.get("CURATED_PRJ")
-CURATED_BQ_DATASET = os.environ.get("CURATED_BQ_DATASET")
-CURATED_GCS = os.environ.get("CURATED_GCS")
-LAND_PRJ = os.environ.get("LAND_PRJ")
-LAND_GCS = os.environ.get("LAND_GCS")
-PROCESSING_GCS = os.environ.get("PROCESSING_GCS")
-PROCESSING_SA = os.environ.get("PROCESSING_SA")
-PROCESSING_PRJ = os.environ.get("PROCESSING_PRJ")
-PROCESSING_SUBNET = os.environ.get("PROCESSING_SUBNET")
-PROCESSING_VPC = os.environ.get("PROCESSING_VPC")
-DP_KMS_KEY = os.environ.get("DP_KMS_KEY", "")
-DP_REGION = os.environ.get("DP_REGION")
-DP_ZONE = os.environ.get("DP_REGION") + "-b"
+BQ_LOCATION = Variable.get("BQ_LOCATION")
+CURATED_PRJ = Variable.get("CURATED_PRJ")
+CURATED_BQ_DATASET = Variable.get("CURATED_BQ_DATASET")
+CURATED_GCS = Variable.get("CURATED_GCS")
+LAND_PRJ = Variable.get("LAND_PRJ")
+LAND_GCS = Variable.get("LAND_GCS")
+PROCESSING_GCS = Variable.get("PROCESSING_GCS")
+PROCESSING_SA = Variable.get("PROCESSING_SA")
+PROCESSING_PRJ = Variable.get("PROCESSING_PRJ")
+PROCESSING_SUBNET = Variable.get("PROCESSING_SUBNET")
+PROCESSING_VPC = Variable.get("PROCESSING_VPC")
+DP_KMS_KEY = Variable.get("DP_KMS_KEY", "")
+DP_REGION = Variable.get("DP_REGION")
+DP_ZONE = Variable.get("DP_REGION") + "-b"
# --------------------------------------------------------------------------------
# Set default arguments
@@ -75,23 +70,23 @@ with models.DAG(
'delete_tables_dag',
default_args=default_args,
schedule_interval=None) as dag:
- start = dummy.DummyOperator(
+ start = empty.EmptyOperator(
task_id='start',
trigger_rule='all_success'
)
- end = dummy.DummyOperator(
+ end = empty.EmptyOperator(
task_id='end',
trigger_rule='all_success'
)
# Bigquery Tables deleted here for demo porpuse.
# Consider a dedicated pipeline or tool for a real life scenario.
- with TaskGroup('delete_table') as delte_table:
+ with TaskGroup('delete_table') as delete_table:
delete_table_customers = BigQueryDeleteTableOperator(
task_id="delete_table_customers",
deletion_dataset_table=CURATED_PRJ+"."+CURATED_BQ_DATASET+".customers",
impersonation_chain=[PROCESSING_SA]
)
- start >> delte_table >> end
+ start >> delete_table >> end
diff --git a/blueprints/data-solutions/data-platform-minimal/demo/dag_orchestrate_pyspark.py b/blueprints/data-solutions/data-platform-minimal/demo/dag_orchestrate_pyspark.py
index 0a68dbc0..4258e7e4 100644
--- a/blueprints/data-solutions/data-platform-minimal/demo/dag_orchestrate_pyspark.py
+++ b/blueprints/data-solutions/data-platform-minimal/demo/dag_orchestrate_pyspark.py
@@ -14,41 +14,38 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import datetime
import time
-import os
from airflow import models
-from airflow.operators import dummy
+from airflow.models.variable import Variable
+from airflow.operators import empty
from airflow.providers.google.cloud.operators.dataproc import (
- DataprocCreateBatchOperator, DataprocDeleteBatchOperator, DataprocGetBatchOperator, DataprocListBatchesOperator
-
+ DataprocCreateBatchOperator
)
from airflow.utils.dates import days_ago
# --------------------------------------------------------------------------------
# Get variables
# --------------------------------------------------------------------------------
-BQ_LOCATION = os.environ.get("BQ_LOCATION")
-CURATED_BQ_DATASET = os.environ.get("CURATED_BQ_DATASET")
-CURATED_GCS = os.environ.get("CURATED_GCS")
-CURATED_PRJ = os.environ.get("CURATED_PRJ")
-DP_KMS_KEY = os.environ.get("DP_KMS_KEY", "")
-DP_REGION = os.environ.get("DP_REGION")
-GCP_REGION = os.environ.get("GCP_REGION")
-LAND_PRJ = os.environ.get("LAND_PRJ")
-LAND_BQ_DATASET = os.environ.get("LAND_BQ_DATASET")
-LAND_GCS = os.environ.get("LAND_GCS")
-PHS_CLUSTER_NAME = os.environ.get("PHS_CLUSTER_NAME")
-PROCESSING_GCS = os.environ.get("PROCESSING_GCS")
-PROCESSING_PRJ = os.environ.get("PROCESSING_PRJ")
-PROCESSING_SA = os.environ.get("PROCESSING_SA")
-PROCESSING_SUBNET = os.environ.get("PROCESSING_SUBNET")
-PROCESSING_VPC = os.environ.get("PROCESSING_VPC")
+BQ_LOCATION = Variable.get("BQ_LOCATION")
+CURATED_BQ_DATASET = Variable.get("CURATED_BQ_DATASET")
+CURATED_GCS = Variable.get("CURATED_GCS")
+CURATED_PRJ = Variable.get("CURATED_PRJ")
+DP_KMS_KEY = Variable.get("DP_KMS_KEY", "")
+DP_REGION = Variable.get("DP_REGION")
+LAND_PRJ = Variable.get("LAND_PRJ")
+LAND_BQ_DATASET = Variable.get("LAND_BQ_DATASET")
+LAND_GCS = Variable.get("LAND_GCS")
+PHS_CLUSTER_NAME = Variable.get("PHS_CLUSTER_NAME")
+PROCESSING_GCS = Variable.get("PROCESSING_GCS")
+PROCESSING_PRJ = Variable.get("PROCESSING_PRJ")
+PROCESSING_SA = Variable.get("PROCESSING_SA")
+PROCESSING_SUBNET = Variable.get("PROCESSING_SUBNET")
+PROCESSING_VPC = Variable.get("PROCESSING_VPC")
-PYTHON_FILE_LOCATION = PROCESSING_GCS+"/pyspark_sort.py"
-PHS_CLUSTER_PATH = "projects/"+PROCESSING_PRJ+"/regions/"+DP_REGION+"/clusters/"+PHS_CLUSTER_NAME
-BATCH_ID = "batch-create-phs-"+str(int(time.time()))
+PYTHON_FILE_LOCATION = PROCESSING_GCS + "/pyspark_sort.py"
+PHS_CLUSTER_PATH = f"projects/{PROCESSING_PRJ}/regions/{DP_REGION}/clusters/{PHS_CLUSTER_NAME}"
+BATCH_ID = "batch-create-phs-" + str(int(time.time()))
default_args = {
# Tell airflow to start one day ago, so that it runs as soon as you upload it
@@ -60,12 +57,12 @@ with models.DAG(
default_args=default_args, # The interval with which to schedule the DAG
schedule_interval=None, # Override to match your needs
) as dag:
- start = dummy.DummyOperator(
+ start = empty.EmptyOperator(
task_id='start',
trigger_rule='all_success'
)
- end = dummy.DummyOperator(
+ end = empty.EmptyOperator(
task_id='end',
trigger_rule='all_success'
)
diff --git a/blueprints/data-solutions/data-platform-minimal/variables.tf b/blueprints/data-solutions/data-platform-minimal/variables.tf
index 0dc29003..0bd1deed 100644
--- a/blueprints/data-solutions/data-platform-minimal/variables.tf
+++ b/blueprints/data-solutions/data-platform-minimal/variables.tf
@@ -19,10 +19,11 @@ variable "composer_config" {
type = object({
environment_size = optional(string, "ENVIRONMENT_SIZE_SMALL")
software_config = optional(object({
- airflow_config_overrides = optional(map(string), {})
- pypi_packages = optional(map(string), {})
- env_variables = optional(map(string), {})
- image_version = optional(string, "composer-2-airflow-2")
+ airflow_config_overrides = optional(map(string), {})
+ pypi_packages = optional(map(string), {})
+ env_variables = optional(map(string), {})
+ image_version = optional(string, "composer-2-airflow-2")
+ cloud_data_lineage_integration = optional(bool, true)
}), {})
web_server_access_control = optional(map(string), {})
workloads_config = optional(object({
diff --git a/blueprints/data-solutions/data-playground/versions.tf b/blueprints/data-solutions/data-playground/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/data-solutions/data-playground/versions.tf
+++ b/blueprints/data-solutions/data-playground/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/data-solutions/gcs-to-bq-with-least-privileges/kms.tf b/blueprints/data-solutions/gcs-to-bq-with-least-privileges/kms.tf
index 5e616630..722016b7 100644
--- a/blueprints/data-solutions/gcs-to-bq-with-least-privileges/kms.tf
+++ b/blueprints/data-solutions/gcs-to-bq-with-least-privileges/kms.tf
@@ -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.
@@ -21,26 +21,27 @@ module "kms" {
location = var.region
}
keys = {
- key-df = null
- key-gcs = null
- key-bq = null
- }
- key_iam = {
- key-gcs = {
- "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [
- "serviceAccount:${module.project.service_accounts.robots.storage}"
- ]
- },
- key-bq = {
- "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [
- "serviceAccount:${module.project.service_accounts.robots.bq}"
- ]
- },
key-df = {
- "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [
- "serviceAccount:${module.project.service_accounts.robots.dataflow}",
- "serviceAccount:${module.project.service_accounts.robots.compute}",
- ]
+ iam = {
+ "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [
+ "serviceAccount:${module.project.service_accounts.robots.dataflow}",
+ "serviceAccount:${module.project.service_accounts.robots.compute}",
+ ]
+ }
+ }
+ key-gcs = {
+ iam = {
+ "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [
+ "serviceAccount:${module.project.service_accounts.robots.storage}"
+ ]
+ }
+ }
+ key-bq = {
+ iam = {
+ "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [
+ "serviceAccount:${module.project.service_accounts.robots.bq}"
+ ]
+ }
}
}
}
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 e4f7404f..91a91a31 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
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/data-solutions/shielded-folder/README.md b/blueprints/data-solutions/shielded-folder/README.md
index ed177d27..72a6b69f 100644
--- a/blueprints/data-solutions/shielded-folder/README.md
+++ b/blueprints/data-solutions/shielded-folder/README.md
@@ -159,18 +159,18 @@ terraform apply
|---|---|:---:|:---:|:---:|
| [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#L129) | Organization details. | object({…})
| ✓ | |
-| [prefix](variables.tf#L137) | Prefix used for resources that need unique names. | string
| ✓ | |
-| [project_config](variables.tf#L142) | 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({…})
| ✓ | |
+| [organization](variables.tf#L148) | Organization details. | object({…})
| ✓ | |
+| [prefix](variables.tf#L156) | Prefix used for resources that need unique names. | string
| ✓ | |
+| [project_config](variables.tf#L161) | 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#L87) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…})
| | {…}
|
-| [log_sinks](variables.tf#L104) | Org-level log sinks, in name => {type, filter} format. | map(object({…}))
| | {…}
|
-| [vpc_sc_access_levels](variables.tf#L162) | VPC SC access level definitions. | map(object({…}))
| | {}
|
-| [vpc_sc_egress_policies](variables.tf#L191) | VPC SC egress policy definitions. | map(object({…}))
| | {}
|
-| [vpc_sc_ingress_policies](variables.tf#L211) | VPC SC ingress policy definitions. | map(object({…}))
| | {}
|
+| [kms_keys](variables.tf#L75) | KMS keys to create, keyed by name. | map(object({…}))
| | {}
|
+| [log_locations](variables.tf#L111) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…})
| | {}
|
+| [log_sinks](variables.tf#L123) | Org-level log sinks, in name => {type, filter} format. | map(object({…}))
| | {…}
|
+| [vpc_sc_access_levels](variables.tf#L181) | VPC SC access level definitions. | map(object({…}))
| | {}
|
+| [vpc_sc_egress_policies](variables.tf#L210) | VPC SC egress policy definitions. | map(object({…}))
| | {}
|
+| [vpc_sc_ingress_policies](variables.tf#L230) | VPC SC ingress policy definitions. | map(object({…}))
| | {}
|
## Outputs
diff --git a/blueprints/data-solutions/shielded-folder/kms.tf b/blueprints/data-solutions/shielded-folder/kms.tf
index 9953d458..4a634fcc 100644
--- a/blueprints/data-solutions/shielded-folder/kms.tf
+++ b/blueprints/data-solutions/shielded-folder/kms.tf
@@ -17,12 +17,17 @@
# tfdoc:file:description Security project, Cloud KMS and Secret Manager resources.
locals {
+ # list of locations with keys
kms_locations = distinct(flatten([
for k, v in var.kms_keys : v.locations
]))
+ # map { location -> { key_name -> key_details } }
kms_locations_keys = {
- for loc in local.kms_locations : loc => {
- for k, v in var.kms_keys : k => v if contains(v.locations, loc)
+ 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([
@@ -30,17 +35,14 @@ locals {
]))
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"
}
@@ -88,12 +90,6 @@ module "sec-kms" {
location = each.key
name = "sec-${each.key}"
}
- key_iam = {
- for k, v in local.kms_locations_keys[each.key] : k => v.iam
- }
- key_iam_bindings_additive = {
- for k, v in local.kms_locations_keys[each.key] : k => v.iam_bindings_additive
- }
keys = local.kms_locations_keys[each.key]
}
diff --git a/blueprints/data-solutions/shielded-folder/variables.tf b/blueprints/data-solutions/shielded-folder/variables.tf
index 5bb80d57..03fea7c4 100644
--- a/blueprints/data-solutions/shielded-folder/variables.tf
+++ b/blueprints/data-solutions/shielded-folder/variables.tf
@@ -75,11 +75,35 @@ variable "groups" {
variable "kms_keys" {
description = "KMS keys to create, keyed by name."
type = map(object({
- iam = optional(map(list(string)), {})
- iam_bindings_additive = optional(map(map(any)), {})
- labels = optional(map(string), {})
- locations = optional(list(string), ["global", "europe", "europe-west1"])
- rotation_period = optional(string, "7776000s")
+ labels = optional(map(string))
+ locations = optional(list(string), ["global", "europe", "europe-west1"])
+ rotation_period = optional(string, "7776000s")
+ purpose = optional(string, "ENCRYPT_DECRYPT")
+ skip_initial_version_creation = optional(bool, false)
+ version_template = optional(object({
+ algorithm = string
+ protection_level = optional(string, "SOFTWARE")
+ }))
+
+ iam = optional(map(list(string)), {})
+ iam_bindings = optional(map(object({
+ members = list(string)
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
+ iam_bindings_additive = optional(map(object({
+ member = string
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
+
}))
default = {}
}
@@ -92,12 +116,7 @@ variable "log_locations" {
logging = optional(string, "global")
pubsub = optional(string, "global")
})
- default = {
- bq = "europe"
- storage = "europe"
- logging = "global"
- pubsub = null
- }
+ default = {}
nullable = false
}
diff --git a/blueprints/factories/net-vpc-firewall-yaml/versions.tf b/blueprints/factories/net-vpc-firewall-yaml/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/factories/net-vpc-firewall-yaml/versions.tf
+++ b/blueprints/factories/net-vpc-firewall-yaml/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/factories/project-factory/README.md b/blueprints/factories/project-factory/README.md
index d144a11a..74682ae9 100644
--- a/blueprints/factories/project-factory/README.md
+++ b/blueprints/factories/project-factory/README.md
@@ -55,6 +55,7 @@ billing_account: 012345-67890A-BCDEF0
labels:
app: app-1
team: foo
+parent: folders/12345678
service_encryption_key_ids:
compute:
- projects/kms-central-prj/locations/europe-west3/keyRings/my-keyring/cryptoKeys/europe3-gce
@@ -71,6 +72,7 @@ service_accounts:
labels:
app: app-1
team: foo
+parent: folders/12345678
service_accounts:
app-2-be: {}
@@ -81,10 +83,10 @@ service_accounts:
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [factory_data](variables.tf#L83) | Project data from either YAML files or externally parsed data. | object({…})
| ✓ | |
-| [data_defaults](variables.tf#L17) | Optional default values used when corresponding project data from files are missing. | object({…})
| | {}
|
-| [data_merges](variables.tf#L44) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | object({…})
| | {}
|
-| [data_overrides](variables.tf#L63) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | object({…})
| | {}
|
+| [factory_data](variables.tf#L85) | Project data from either YAML files or externally parsed data. | object({…})
| ✓ | |
+| [data_defaults](variables.tf#L17) | Optional default values used when corresponding project data from files are missing. | object({…})
| | {}
|
+| [data_merges](variables.tf#L45) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | object({…})
| | {}
|
+| [data_overrides](variables.tf#L64) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | object({…})
| | {}
|
## Outputs
diff --git a/blueprints/factories/project-factory/factory.tf b/blueprints/factories/project-factory/factory.tf
index dac843df..0390b055 100644
--- a/blueprints/factories/project-factory/factory.tf
+++ b/blueprints/factories/project-factory/factory.tf
@@ -28,11 +28,11 @@ locals {
)
projects = {
for k, v in local._data : k => merge(v, {
- billing_account = coalesce(
+ billing_account = try(coalesce(
var.data_overrides.billing_account,
try(v.billing_account, null),
var.data_defaults.billing_account
- )
+ ), null)
contacts = coalesce(
var.data_overrides.contacts,
try(v.contacts, null),
@@ -46,6 +46,11 @@ locals {
try(v.metric_scopes, null),
var.data_defaults.metric_scopes
)
+ parent = coalesce(
+ var.data_overrides.parent,
+ try(v.parent, null),
+ var.data_defaults.parent
+ )
prefix = coalesce(
var.data_overrides.prefix,
try(v.prefix, null),
diff --git a/blueprints/factories/project-factory/main.tf b/blueprints/factories/project-factory/main.tf
index 7d173a11..9a230063 100644
--- a/blueprints/factories/project-factory/main.tf
+++ b/blueprints/factories/project-factory/main.tf
@@ -33,11 +33,13 @@ module "projects" {
iam = try(each.value.iam, {})
iam_bindings = try(each.value.iam_bindings, {})
iam_bindings_additive = try(each.value.iam_bindings_additive, {})
- labels = each.value.labels
- lien_reason = try(each.value.lien_reason, null)
- logging_data_access = try(each.value.logging_data_access, {})
- logging_exclusions = try(each.value.logging_exclusions, {})
- logging_sinks = try(each.value.logging_sinks, {})
+ labels = merge(
+ each.value.labels, var.data_merges.labels
+ )
+ lien_reason = try(each.value.lien_reason, null)
+ logging_data_access = try(each.value.logging_data_access, {})
+ logging_exclusions = try(each.value.logging_exclusions, {})
+ logging_sinks = try(each.value.logging_sinks, {})
metric_scopes = distinct(concat(
each.value.metric_scopes, var.data_merges.metric_scopes
))
diff --git a/blueprints/factories/project-factory/variables.tf b/blueprints/factories/project-factory/variables.tf
index 67917846..d7176474 100644
--- a/blueprints/factories/project-factory/variables.tf
+++ b/blueprints/factories/project-factory/variables.tf
@@ -21,6 +21,7 @@ variable "data_defaults" {
contacts = optional(map(list(string)), {})
labels = optional(map(string), {})
metric_scopes = optional(list(string), [])
+ parent = optional(string)
prefix = optional(string)
service_encryption_key_ids = optional(map(list(string)), {})
service_perimeter_bridges = optional(list(string), [])
@@ -65,6 +66,7 @@ variable "data_overrides" {
type = object({
billing_account = optional(string)
contacts = optional(map(list(string)))
+ parent = optional(string)
prefix = optional(string)
service_encryption_key_ids = optional(map(list(string)))
service_perimeter_bridges = optional(list(string))
diff --git a/blueprints/gke/autopilot/cluster.tf b/blueprints/gke/autopilot/cluster.tf
index ed6fa661..49409c44 100644
--- a/blueprints/gke/autopilot/cluster.tf
+++ b/blueprints/gke/autopilot/cluster.tf
@@ -20,12 +20,9 @@ module "cluster" {
name = "cluster"
location = var.region
vpc_config = {
- network = module.vpc.self_link
- subnetwork = module.vpc.subnet_self_links["${var.region}/subnet-cluster"]
- secondary_range_names = {
- pods = "pods"
- services = "services"
- }
+ network = module.vpc.self_link
+ subnetwork = module.vpc.subnet_self_links["${var.region}/subnet-cluster"]
+ secondary_range_names = {}
master_authorized_ranges = var.cluster_network_config.master_authorized_cidr_blocks
master_ipv4_cidr_block = var.cluster_network_config.master_cidr_block
}
@@ -33,8 +30,17 @@ module "cluster" {
# autopilot = true
# }
# monitoring_config = {
- # enenable_components = ["SYSTEM_COMPONENTS"]
- # managed_prometheus = true
+ # # (Optional) control plane metrics
+ # enable_api_server_metrics = true
+ # enable_controller_manager_metrics = true
+ # enable_scheduler_metrics = true
+ # # (Optional) kube state metrics
+ # enable_daemonset_metrics = true
+ # enable_deployment_metrics = true
+ # enable_hpa_metrics = true
+ # enable_pod_metrics = true
+ # enable_statefulset_metrics = true
+ # enable_storage_metrics = true
# }
# cluster_autoscaling = {
# auto_provisioning_defaults = {
@@ -51,4 +57,4 @@ module "node_sa" {
source = "../../../modules/iam-service-account"
project_id = module.project.project_id
name = "sa-node"
-}
\ No newline at end of file
+}
diff --git a/blueprints/gke/binauthz/main.tf b/blueprints/gke/binauthz/main.tf
index 2eac7c56..8cff68a0 100644
--- a/blueprints/gke/binauthz/main.tf
+++ b/blueprints/gke/binauthz/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.
@@ -115,20 +115,16 @@ module "kms" {
project_id = module.project.project_id
keyring = { location = var.region, name = "test-keyring" }
keyring_create = true
- keys = { test-key = null }
- key_purpose = {
+ keys = {
test-key = {
purpose = "ASYMMETRIC_SIGN"
version_template = {
- algorithm = "RSA_SIGN_PKCS1_4096_SHA512"
- protection_level = null
+ algorithm = "RSA_SIGN_PKCS1_4096_SHA512"
+ }
+ iam = {
+ "roles/cloudkms.publicKeyViewer" = [module.image_cb_sa.iam_email]
+ "roles/cloudkms.signer" = [module.image_cb_sa.iam_email]
}
- }
- }
- key_iam = {
- test-key = {
- "roles/cloudkms.publicKeyViewer" = [module.image_cb_sa.iam_email]
- "roles/cloudkms.signer" = [module.image_cb_sa.iam_email]
}
}
}
diff --git a/blueprints/gke/multitenant-fleet/README.md b/blueprints/gke/multitenant-fleet/README.md
index baaf288f..ed89a878 100644
--- a/blueprints/gke/multitenant-fleet/README.md
+++ b/blueprints/gke/multitenant-fleet/README.md
@@ -244,21 +244,21 @@ module "gke" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [billing_account_id](variables.tf#L17) | Billing account id. | string
| ✓ | |
-| [folder_id](variables.tf#L138) | Folder used for the GKE project in folders/nnnnnnnnnnn format. | string
| ✓ | |
-| [prefix](variables.tf#L189) | Prefix used for resource names. | string
| ✓ | |
-| [project_id](variables.tf#L198) | ID of the project that will contain all the clusters. | string
| ✓ | |
-| [vpc_config](variables.tf#L210) | Shared VPC project and VPC details. | object({…})
| ✓ | |
-| [clusters](variables.tf#L22) | Clusters configuration. Refer to the gke-cluster module for type details. | map(object({…}))
| | {}
|
-| [fleet_configmanagement_clusters](variables.tf#L76) | Config management features enabled on specific sets of member clusters, in config name => [cluster name] format. | map(list(string))
| | {}
|
-| [fleet_configmanagement_templates](variables.tf#L83) | Sets of config management configurations that can be applied to member clusters, in config name => {options} format. | map(object({…}))
| | {}
|
-| [fleet_features](variables.tf#L118) | Enable and configure fleet features. Set to null to disable GKE Hub if fleet workload identity is not used. | object({…})
| | null
|
-| [fleet_workload_identity](variables.tf#L131) | Use Fleet Workload Identity for clusters. Enables GKE Hub if set to true. | bool
| | false
|
-| [group_iam](variables.tf#L143) | Project-level IAM bindings for groups. Use group emails as keys, list of roles as values. | map(list(string))
| | {}
|
-| [iam](variables.tf#L150) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
-| [labels](variables.tf#L157) | Project-level labels. | map(string)
| | {}
|
-| [nodepools](variables.tf#L163) | Nodepools configuration. Refer to the gke-nodepool module for type details. | map(map(object({…})))
| | {}
|
-| [project_services](variables.tf#L203) | Additional project services to enable. | list(string)
| | []
|
+| [billing_account_id](variables.tf#L17) | Billing account ID. | string
| ✓ | |
+| [folder_id](variables.tf#L154) | Folder used for the GKE project in folders/nnnnnnnnnnn format. | string
| ✓ | |
+| [prefix](variables.tf#L205) | Prefix used for resource names. | string
| ✓ | |
+| [project_id](variables.tf#L214) | ID of the project that will contain all the clusters. | string
| ✓ | |
+| [vpc_config](variables.tf#L226) | Shared VPC project and VPC details. | object({…})
| ✓ | |
+| [clusters](variables.tf#L22) | Clusters configuration. Refer to the gke-cluster module for type details. | map(object({…}))
| | {}
|
+| [fleet_configmanagement_clusters](variables.tf#L92) | Config management features enabled on specific sets of member clusters, in config name => [cluster name] format. | map(list(string))
| | {}
|
+| [fleet_configmanagement_templates](variables.tf#L99) | Sets of config management configurations that can be applied to member clusters, in config name => {options} format. | map(object({…}))
| | {}
|
+| [fleet_features](variables.tf#L134) | Enable and configure fleet features. Set to null to disable GKE Hub if fleet workload identity is not used. | object({…})
| | null
|
+| [fleet_workload_identity](variables.tf#L147) | Use Fleet Workload Identity for clusters. Enables GKE Hub if set to true. | bool
| | false
|
+| [group_iam](variables.tf#L159) | Project-level IAM bindings for groups. Use group emails as keys, list of roles as values. | map(list(string))
| | {}
|
+| [iam](variables.tf#L166) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
+| [labels](variables.tf#L173) | Project-level labels. | map(string)
| | {}
|
+| [nodepools](variables.tf#L179) | Nodepools configuration. Refer to the gke-nodepool module for type details. | map(map(object({…})))
| | {}
|
+| [project_services](variables.tf#L219) | Additional project services to enable. | list(string)
| | []
|
## Outputs
diff --git a/blueprints/gke/multitenant-fleet/variables.tf b/blueprints/gke/multitenant-fleet/variables.tf
index 2461ea8a..5d34440f 100644
--- a/blueprints/gke/multitenant-fleet/variables.tf
+++ b/blueprints/gke/multitenant-fleet/variables.tf
@@ -15,7 +15,7 @@
*/
variable "billing_account_id" {
- description = "Billing account id."
+ description = "Billing account ID."
type = string
}
@@ -48,9 +48,25 @@ variable "clusters" {
max_pods_per_node = optional(number, 110)
min_master_version = optional(string)
monitoring_config = optional(object({
- enable_components = optional(list(string), ["SYSTEM_COMPONENTS"])
- managed_prometheus = optional(bool)
- }))
+ enable_system_metrics = optional(bool, true)
+
+ # (Optional) control plane metrics
+ enable_api_server_metrics = optional(bool, false)
+ enable_controller_manager_metrics = optional(bool, false)
+ enable_scheduler_metrics = optional(bool, false)
+
+ # (Optional) kube state metrics
+ enable_daemonset_metrics = optional(bool, false)
+ enable_deployment_metrics = optional(bool, false)
+ enable_hpa_metrics = optional(bool, false)
+ enable_pod_metrics = optional(bool, false)
+ enable_statefulset_metrics = optional(bool, false)
+ enable_storage_metrics = optional(bool, false)
+
+ # Google Cloud Managed Service for Prometheus
+ enable_managed_prometheus = optional(bool, true)
+ }), {})
+
node_locations = optional(list(string))
private_cluster_config = optional(any)
release_channel = optional(string)
diff --git a/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/versions.tf b/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/versions.tf
+++ b/blueprints/networking/__need_fixing/nginx-reverse-proxy-cluster/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/networking/__need_fixing/onprem-google-access-dns/versions.tf b/blueprints/networking/__need_fixing/onprem-google-access-dns/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/networking/__need_fixing/onprem-google-access-dns/versions.tf
+++ b/blueprints/networking/__need_fixing/onprem-google-access-dns/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/networking/decentralized-firewall/versions.tf b/blueprints/networking/decentralized-firewall/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/networking/decentralized-firewall/versions.tf
+++ b/blueprints/networking/decentralized-firewall/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/networking/filtering-proxy-psc/versions.tf b/blueprints/networking/filtering-proxy-psc/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/networking/filtering-proxy-psc/versions.tf
+++ b/blueprints/networking/filtering-proxy-psc/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/networking/filtering-proxy/versions.tf b/blueprints/networking/filtering-proxy/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/networking/filtering-proxy/versions.tf
+++ b/blueprints/networking/filtering-proxy/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/networking/hub-and-spoke-peering/versions.tf b/blueprints/networking/hub-and-spoke-peering/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/networking/hub-and-spoke-peering/versions.tf
+++ b/blueprints/networking/hub-and-spoke-peering/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/networking/hub-and-spoke-vpn/versions.tf b/blueprints/networking/hub-and-spoke-vpn/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/networking/hub-and-spoke-vpn/versions.tf
+++ b/blueprints/networking/hub-and-spoke-vpn/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/networking/ilb-next-hop/versions.tf b/blueprints/networking/ilb-next-hop/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/networking/ilb-next-hop/versions.tf
+++ b/blueprints/networking/ilb-next-hop/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.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 e4f7404f..91a91a31 100644
--- a/blueprints/networking/private-cloud-function-from-onprem/versions.tf
+++ b/blueprints/networking/private-cloud-function-from-onprem/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/networking/shared-vpc-gke/main.tf b/blueprints/networking/shared-vpc-gke/main.tf
index 302ce735..88f48463 100644
--- a/blueprints/networking/shared-vpc-gke/main.tf
+++ b/blueprints/networking/shared-vpc-gke/main.tf
@@ -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.
@@ -102,6 +102,11 @@ module "vpc-shared" {
ip_cidr_range = var.ip_ranges.gce
name = "gce"
region = var.region
+ iam = {
+ "roles/compute.networkUser" = concat(var.owners_gce, [
+ "serviceAccount:${module.project-svc-gce.service_accounts.cloud_services}",
+ ])
+ }
},
{
ip_cidr_range = var.ip_ranges.gke
@@ -111,24 +116,17 @@ module "vpc-shared" {
pods = var.ip_secondary_ranges.gke-pods
services = var.ip_secondary_ranges.gke-services
}
+ iam = {
+ "roles/compute.networkUser" = concat(var.owners_gke, [
+ "serviceAccount:${module.project-svc-gke.service_accounts.cloud_services}",
+ "serviceAccount:${module.project-svc-gke.service_accounts.robots.container-engine}",
+ ])
+ "roles/compute.securityAdmin" = [
+ "serviceAccount:${module.project-svc-gke.service_accounts.robots.container-engine}",
+ ]
+ }
}
]
- subnet_iam = {
- "${var.region}/gce" = {
- "roles/compute.networkUser" = concat(var.owners_gce, [
- "serviceAccount:${module.project-svc-gce.service_accounts.cloud_services}",
- ])
- }
- "${var.region}/gke" = {
- "roles/compute.networkUser" = concat(var.owners_gke, [
- "serviceAccount:${module.project-svc-gke.service_accounts.cloud_services}",
- "serviceAccount:${module.project-svc-gke.service_accounts.robots.container-engine}",
- ])
- "roles/compute.securityAdmin" = [
- "serviceAccount:${module.project-svc-gke.service_accounts.robots.container-engine}",
- ]
- }
- }
}
module "vpc-shared-firewall" {
diff --git a/blueprints/networking/shared-vpc-gke/versions.tf b/blueprints/networking/shared-vpc-gke/versions.tf
index e4f7404f..91a91a31 100644
--- a/blueprints/networking/shared-vpc-gke/versions.tf
+++ b/blueprints/networking/shared-vpc-gke/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/blueprints/serverless/cloud-run-explore/README.md b/blueprints/serverless/cloud-run-explore/README.md
index ff1454c1..9005165f 100644
--- a/blueprints/serverless/cloud-run-explore/README.md
+++ b/blueprints/serverless/cloud-run-explore/README.md
@@ -214,5 +214,5 @@ module "test" {
}
}
-# tftest modules=4 resources=18
+# tftest modules=4 resources=19
```
diff --git a/blueprints/third-party-solutions/README.md b/blueprints/third-party-solutions/README.md
index c81bc144..62e3304e 100644
--- a/blueprints/third-party-solutions/README.md
+++ b/blueprints/third-party-solutions/README.md
@@ -6,12 +6,18 @@ The blueprints in this folder show how to automate installation of specific thir
### OpenShift cluster bootstrap on Shared VPC
- This [example](./openshift/) shows how to quickly bootstrap an OpenShift 4.7 cluster on GCP, using typical enterprise features like Shared VPC and CMEK for instance disks.
+
This [example](./openshift/) shows how to quickly bootstrap an OpenShift 4.7 cluster on GCP, using typical enterprise features like Shared VPC and CMEK for instance disks.
This [example](./wordpress/cloudrun/) shows how to deploy a functioning new Wordpress website exposed to the public internet via CloudRun and Cloud SQL, with minimal technical overhead.
This [example](./phpipam/) shows how to quickly bootstrap a serverless phpIPAM instance on GCP using Cloud Run. This comes with typical enterprise features like Shared VPC, Cloud Armor with IAP and, possibly, private exposure via Internal Application Load Balancer. Indeed, the script supports deploying the application either publicly via Global Application Load Balancer with restricted access based on IPs (Cloud Armor) and identities (Identity Aware Proxy) or privately via Internal Application Load Balancer.
+ +string
| ✓ | |
+| [project_id](variables.tf#L128) | Project id, references existing project if `project_create` is null. | string
| ✓ | |
+| [admin_principals](variables.tf#L19) | Users, groups and/or service accounts that are assigned roles, in IAM format (`group:foo@example.com`). | list(string)
| | []
|
+| [cloud_run_invoker](variables.tf#L25) | IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone). | string
| | "allUsers"
|
+| [cloudsql_password](variables.tf#L31) | CloudSQL password (will be randomly generated by default). | string
| | null
|
+| [connector](variables.tf#L37) | Existing VPC serverless connector to use if not creating a new one. | string
| | null
|
+| [create_connector](variables.tf#L43) | Should a VPC serverless connector be created or not. | bool
| | true
|
+| [custom_domain](variables.tf#L49) | Cloud Run service custom domain for GLB. | string
| | null
|
+| [iap](variables.tf#L55) | Identity-Aware Proxy for Cloud Run in the LB. | object({…})
| | {}
|
+| [ip_ranges](variables.tf#L67) | CIDR blocks: VPC serverless connector, Private Service Access(PSA) for CloudSQL, CloudSQL VPC. | object({…})
| | {…}
|
+| [phpipam_config](variables.tf#L81) | PHPIpam configuration. | object({…})
| | {…}
|
+| [phpipam_exposure](variables.tf#L93) | Whether to expose the application publicly via GLB or internally via ILB, default GLB. | string
| | "EXTERNAL"
|
+| [phpipam_password](variables.tf#L103) | Password for the phpipam user (will be randomly generated by default). | string
| | null
|
+| [project_create](variables.tf#L119) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…})
| | null
|
+| [region](variables.tf#L133) | Region for the created resources. | string
| | "europe-west4"
|
+| [security_policy](variables.tf#L139) | Security policy (Cloud Armor) to enforce in the LB. | object({…})
| | {}
|
+| [vpc_config](variables.tf#L149) | VPC Network and subnetwork self links for internal LB setup. | object({…})
| | null
|
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| [cloud_run_service](outputs.tf#L17) | CloudRun service URL. | ✓ |
+| [cloudsql_password](outputs.tf#L23) | CloudSQL password. | ✓ |
+| [phpipam_ip_address](outputs.tf#L29) | PHPIPAM IP Address either external or internal according to app exposure. | |
+| [phpipam_password](outputs.tf#L34) | PHPIPAM user password. | ✓ |
+| [phpipam_url](outputs.tf#L40) | PHPIPAM website url. | |
+| [phpipam_user](outputs.tf#L45) | PHPIPAM username. | |
+
+## Test
+
+```hcl
+module "test" {
+ source = "./fabric/blueprints/third-party-solutions/phpipam"
+ admin_principals = ["group:foo@example.com"]
+ prefix = "test"
+ project_create = {
+ billing_account_id = "1234-ABCD-1234"
+ parent = "folders/1234563"
+ }
+ project_id = "test-prj"
+}
+# tftest modules=7 resources=43
+```
diff --git a/blueprints/third-party-solutions/phpipam/cloudsql.tf b/blueprints/third-party-solutions/phpipam/cloudsql.tf
new file mode 100644
index 00000000..0dc89b9a
--- /dev/null
+++ b/blueprints/third-party-solutions/phpipam/cloudsql.tf
@@ -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.
+ */
+
+# Set up CloudSQL
+module "cloudsql" {
+ source = "../../../modules/cloudsql-instance"
+ project_id = module.project.project_id
+ name = "${var.prefix}-mysql"
+ database_version = local.cloudsql_conf.database_version
+ databases = [local.cloudsql_conf.db]
+ network = local.network
+ prefix = var.prefix
+ region = var.region
+ tier = local.cloudsql_conf.tier
+ users = {
+ "${local.cloudsql_conf.user}" = var.cloudsql_password
+ }
+}
diff --git a/blueprints/third-party-solutions/phpipam/diagrams/phpipam.excalidraw b/blueprints/third-party-solutions/phpipam/diagrams/phpipam.excalidraw
new file mode 100644
index 00000000..f9896973
--- /dev/null
+++ b/blueprints/third-party-solutions/phpipam/diagrams/phpipam.excalidraw
@@ -0,0 +1,4821 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "image",
+ "version": 481,
+ "versionNonce": 537873588,
+ "isDeleted": false,
+ "id": "1XbyXgzt6oISJX4bJOqgJ",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1793.5245329083443,
+ "y": -2032.4573238238672,
+ "strokeColor": "transparent",
+ "backgroundColor": "transparent",
+ "width": 87.0394357600626,
+ "height": 66.35855006612174,
+ "seed": 1031721740,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "status": "saved",
+ "fileId": "af2541e7127d4fdc679914759de23d8bd87e9264",
+ "scale": [
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "line",
+ "version": 660,
+ "versionNonce": 618662028,
+ "isDeleted": false,
+ "id": "KUKHKtwx4uIJzebhF0ZW1",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1766.4112728934037,
+ "y": -2002.44215921633,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 49.11275214513097,
+ "height": 36.92648039990984,
+ "seed": 292309772,
+ "groupIds": [
+ "143QJr2AU36qqThhRKQql",
+ "Nr52ogYxUnYvl_fIZutZ_"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 12.265055829310315,
+ 36.92648039990984
+ ],
+ [
+ 49.11275214513097,
+ 36.92648039990984
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 660,
+ "versionNonce": 527144500,
+ "isDeleted": false,
+ "id": "03GsUk9b3uMGDNQosA5W_",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1815.5240250385348,
+ "y": -1965.5156788164204,
+ "strokeColor": "#000000",
+ "backgroundColor": "#4285f4",
+ "width": 49.11275214513097,
+ "height": 36.92648673988745,
+ "seed": 1447496076,
+ "groupIds": [
+ "143QJr2AU36qqThhRKQql",
+ "Nr52ogYxUnYvl_fIZutZ_"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -36.847696315820656,
+ 0
+ ],
+ [
+ -49.11275214513097,
+ 36.92648673988745
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 661,
+ "versionNonce": 258414348,
+ "isDeleted": false,
+ "id": "wzCWvE55EqWjCYcux-V0-",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1744.875204393739,
+ "y": -1928.5891920765328,
+ "strokeColor": "#000000",
+ "backgroundColor": "#4285f4",
+ "width": 21.536068499663806,
+ "height": 36.92648673988745,
+ "seed": 1581643788,
+ "groupIds": [
+ "143QJr2AU36qqThhRKQql",
+ "Nr52ogYxUnYvl_fIZutZ_"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 12.317583248316135,
+ -6.145664348279347
+ ],
+ [
+ 21.536068499663806,
+ -36.92648673988745
+ ],
+ [
+ 9.21848947799943,
+ -36.92648673988745
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 660,
+ "versionNonce": 1333964724,
+ "isDeleted": false,
+ "id": "AmWl3EKGBEqQ6c-2l5fLr",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1744.875204393739,
+ "y": -2002.44215921633,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 21.536068499663806,
+ "height": 36.92648039990984,
+ "seed": 358815372,
+ "groupIds": [
+ "143QJr2AU36qqThhRKQql",
+ "Nr52ogYxUnYvl_fIZutZ_"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 9.21848947799943,
+ 36.92648039990984
+ ],
+ [
+ 21.536068499663806,
+ 36.92648039990984
+ ],
+ [
+ 12.317583248316135,
+ 6.145659417185632
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 749,
+ "versionNonce": 1945012,
+ "isDeleted": false,
+ "id": "IfVzFaOMx3j3dME8GrhUs",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1743.866131613274,
+ "y": -1927.1534159338844,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 68.65559387207031,
+ "height": 16.7247155341873,
+ "seed": 1482188044,
+ "groupIds": [
+ "Nr52ogYxUnYvl_fIZutZ_"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [
+ {
+ "id": "1hR9SCYu3Fd8-OWyibUBe",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1693311484476,
+ "link": null,
+ "locked": false,
+ "fontSize": 14.539679438756231,
+ "fontFamily": 2,
+ "text": "Cloud Run",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Cloud Run",
+ "lineHeight": 1.150280898876405,
+ "baseline": 13
+ },
+ {
+ "type": "rectangle",
+ "version": 528,
+ "versionNonce": 1009242420,
+ "isDeleted": false,
+ "id": "c9Cux6NrH-jflel7CZ993",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1491.6056084907252,
+ "y": -2130.145262517028,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 701.1157994906367,
+ "height": 539.060730392323,
+ "seed": 304311604,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [],
+ "updated": 1693311392567,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "image",
+ "version": 709,
+ "versionNonce": 519142028,
+ "isDeleted": false,
+ "id": "YYw7gXl4W97O0JXZAMzhR",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1472.3435624427782,
+ "y": -2215.1254824711386,
+ "strokeColor": "transparent",
+ "backgroundColor": "transparent",
+ "width": 372.22474653284047,
+ "height": 248.14983102189362,
+ "seed": 505448500,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311401662,
+ "link": null,
+ "locked": false,
+ "status": "saved",
+ "fileId": "7f10a90d0c745f95f1922694e27ad51a6bf7d09e",
+ "scale": [
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 672,
+ "versionNonce": 1565720756,
+ "isDeleted": false,
+ "id": "hWzo_PR1wR4eOgN7GCA76",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1532.7305950497039,
+ "y": -1813.6835962459613,
+ "strokeColor": "#000000",
+ "backgroundColor": "#a5d8ff",
+ "width": 603.3558049160198,
+ "height": 204.09209589366222,
+ "seed": 168241844,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [],
+ "updated": 1693311318058,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "line",
+ "version": 566,
+ "versionNonce": 1441061940,
+ "isDeleted": false,
+ "id": "YlvLcdA67ovtgbCNjDHKi",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1737.185641562936,
+ "y": -1701.0911930806487,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 30.93220427159039,
+ "height": 29.15983311685192,
+ "seed": 2145151540,
+ "groupIds": [
+ "4QQEDIAcbGDabZcoMrnE9",
+ "aDHYiPoTbYMWlla5qLL3x",
+ "sRnoZ2rKaS2Y28Pz_3wfo"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 12.702040814491287
+ ],
+ [
+ 30.93220427159039,
+ 29.15983311685192
+ ],
+ [
+ 30.93220427159039,
+ 16.457792302360613
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 566,
+ "versionNonce": 210579724,
+ "isDeleted": false,
+ "id": "AJGjPZwDhTGk5R9odCXcm",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1737.185641562936,
+ "y": -1681.4684411845014,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 30.93220427159039,
+ "height": 29.159827080170565,
+ "seed": 314114996,
+ "groupIds": [
+ "-mwo8PSPLEJROjucGMNV9",
+ "aDHYiPoTbYMWlla5qLL3x",
+ "sRnoZ2rKaS2Y28Pz_3wfo"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 12.702040814491289
+ ],
+ [
+ 30.93220427159039,
+ 29.159827080170565
+ ],
+ [
+ 30.93220427159039,
+ 16.45778626567926
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 568,
+ "versionNonce": 853751220,
+ "isDeleted": false,
+ "id": "3JOs7lBPu2ZLdLKA7z_sc",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1768.1178458345266,
+ "y": -1671.9313599637967,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 30.932208799101403,
+ "height": 29.15983311685192,
+ "seed": 566652212,
+ "groupIds": [
+ "Ssfz8of57AsqfvU9c6tHU",
+ "aDHYiPoTbYMWlla5qLL3x",
+ "sRnoZ2rKaS2Y28Pz_3wfo"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 30.932208799101403,
+ -16.45779230236061
+ ],
+ [
+ 30.932208799101403,
+ -29.15983311685192
+ ],
+ [
+ 0,
+ -12.702040814491282
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 568,
+ "versionNonce": 1213935500,
+ "isDeleted": false,
+ "id": "EkV4IW-AJzsz-vh97G-3_",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1768.1178458345266,
+ "y": -1652.3086141043307,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 30.932208799101403,
+ "height": 29.159827080170565,
+ "seed": 241870516,
+ "groupIds": [
+ "9FDnYXoLyfWDYgY1D52rq",
+ "aDHYiPoTbYMWlla5qLL3x",
+ "sRnoZ2rKaS2Y28Pz_3wfo"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 30.932208799101403,
+ -16.457786265679268
+ ],
+ [
+ 30.932208799101403,
+ -29.159827080170565
+ ],
+ [
+ 0,
+ -12.702040814491303
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 568,
+ "versionNonce": 1641701172,
+ "isDeleted": false,
+ "id": "NM4FCQHCF6jXUQseMSki-",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1799.050054633628,
+ "y": -1707.5477105312384,
+ "strokeColor": "#000000",
+ "backgroundColor": "#4285f4",
+ "width": 30.932208799101403,
+ "height": 29.159827834755752,
+ "seed": 491499572,
+ "groupIds": [
+ "z8fP_4PYXZfW0WAcJfUPu",
+ "aDHYiPoTbYMWlla5qLL3x",
+ "sRnoZ2rKaS2Y28Pz_3wfo"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ -12.702037796150632
+ ],
+ [
+ -30.932208799101403,
+ -29.159827834755752
+ ],
+ [
+ -30.932208799101403,
+ -16.457789284019967
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 569,
+ "versionNonce": 115695116,
+ "isDeleted": false,
+ "id": "i8zbbwYqriRe6mw4nCYgK",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1768.1178458345264,
+ "y": -1736.7075383659944,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 30.93220427159039,
+ "height": 29.159827834755752,
+ "seed": 240093620,
+ "groupIds": [
+ "1_yORLPOgADxV6GB4-GxX",
+ "aDHYiPoTbYMWlla5qLL3x",
+ "sRnoZ2rKaS2Y28Pz_3wfo"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -30.93220427159039,
+ 16.457790038605133
+ ],
+ [
+ -30.93220427159039,
+ 29.159827834755752
+ ],
+ [
+ 0,
+ 12.702038550735798
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 566,
+ "versionNonce": 1261459636,
+ "isDeleted": false,
+ "id": "nLcf6dXvi-Dg0zFYxD3my",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1737.185641562936,
+ "y": -1720.376346590407,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 30.93220427159039,
+ "height": 29.20202650117651,
+ "seed": 882651956,
+ "groupIds": [
+ "9KxAiBauJwpvFBEwLOUoa",
+ "aDHYiPoTbYMWlla5qLL3x",
+ "sRnoZ2rKaS2Y28Pz_3wfo"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 12.74424023549724
+ ],
+ [
+ 30.93220427159039,
+ 29.20202650117651
+ ],
+ [
+ 30.93220427159039,
+ 16.457792302360623
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 569,
+ "versionNonce": 19250316,
+ "isDeleted": false,
+ "id": "h6Z26XLvWgyFchxTQvpXL",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1768.1178458345266,
+ "y": -1691.1743200892304,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 30.932208799101403,
+ "height": 29.20202650117651,
+ "seed": 244821172,
+ "groupIds": [
+ "-h4pDIR_NFTn_DviMiwQz",
+ "aDHYiPoTbYMWlla5qLL3x",
+ "sRnoZ2rKaS2Y28Pz_3wfo"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 30.932208799101403,
+ -16.457786265679268
+ ],
+ [
+ 30.932208799101403,
+ -29.20202650117651
+ ],
+ [
+ 0,
+ -12.744234198815874
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 832,
+ "versionNonce": 1519067700,
+ "isDeleted": false,
+ "id": "EgR1hwDJN0PjLrGd5nbjr",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1732.6497236445011,
+ "y": -1650.0718103364015,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 71.41729736328125,
+ "height": 16.969289811898413,
+ "seed": 1575482932,
+ "groupIds": [
+ "10xnq85pGHDASfiSQPfuC",
+ "xRkpIu4RUHYZful5rB5uJ",
+ "m17ghiEvwrmuZDB7XkBzD",
+ "sRnoZ2rKaS2Y28Pz_3wfo"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "fontSize": 14.604231998393391,
+ "fontFamily": 2,
+ "text": "Cloud SQL",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Cloud SQL",
+ "lineHeight": 1.161943319838058,
+ "baseline": 13
+ },
+ {
+ "type": "text",
+ "version": 11,
+ "versionNonce": 1087785740,
+ "isDeleted": false,
+ "id": "8TOTAlez0vKnEmM_OajUD",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1604.7981078973241,
+ "y": -1786.4960135936278,
+ "strokeColor": "#495057",
+ "backgroundColor": "#a5d8ff",
+ "width": 57.572265625,
+ "height": 32.199999999999996,
+ "seed": 631585716,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "fontSize": 28,
+ "fontFamily": 2,
+ "text": "VPC",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VPC",
+ "lineHeight": 1.15,
+ "baseline": 26
+ },
+ {
+ "type": "rectangle",
+ "version": 664,
+ "versionNonce": 701096844,
+ "isDeleted": false,
+ "id": "WZfd3JxWdQGj02Nx1ywUK",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1757.5152703538752,
+ "y": -1843.3509692696189,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#669df6",
+ "width": 30.71336203849055,
+ "height": 5.031485400485886,
+ "seed": 374877708,
+ "groupIds": [
+ "xe_VmpQuCkK-2Lu9CGL8R"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 689,
+ "versionNonce": 450947892,
+ "isDeleted": false,
+ "id": "C58IGor8Vlna-k9T4SG_E",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1758.8846917733617,
+ "y": -1801.2793496901554,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#669df6",
+ "width": 30.71336203849055,
+ "height": 5.031485400485886,
+ "seed": 968789132,
+ "groupIds": [
+ "xe_VmpQuCkK-2Lu9CGL8R"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 715,
+ "versionNonce": 811438604,
+ "isDeleted": false,
+ "id": "2tGkxza5W0dNG2l7A52cu",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 1.5707963267948957,
+ "x": 1778.548046982738,
+ "y": -1821.9384827513968,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#669df6",
+ "width": 30.71336203849055,
+ "height": 5.031485400485886,
+ "seed": 232069900,
+ "groupIds": [
+ "xe_VmpQuCkK-2Lu9CGL8R"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 724,
+ "versionNonce": 998597812,
+ "isDeleted": false,
+ "id": "jfdivAKJ-QRP9KZrYLtRZ",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 1.5707963267948957,
+ "x": 1736.2414295015294,
+ "y": -1820.9136694039341,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#669df6",
+ "width": 30.71336203849055,
+ "height": 5.031485400485886,
+ "seed": 1830148492,
+ "groupIds": [
+ "xe_VmpQuCkK-2Lu9CGL8R"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 964,
+ "versionNonce": 897020044,
+ "isDeleted": false,
+ "id": "qWQrT53vm2Vegs-_kUpu5",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1742.5563882472707,
+ "y": -1849.643844484846,
+ "strokeColor": "#669df6",
+ "backgroundColor": "#aecbfa",
+ "width": 8.85797007441361,
+ "height": 17.70860035786427,
+ "seed": 1387972620,
+ "groupIds": [
+ "6TZ5hG1Ee6MddXwuJXdr5",
+ "xe_VmpQuCkK-2Lu9CGL8R"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 1006,
+ "versionNonce": 341749300,
+ "isDeleted": false,
+ "id": "e3s68qp5MX_7Ayqq5sf77",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1751.7514222120528,
+ "y": -1849.7837438562246,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#669df6",
+ "width": 8.85797007441361,
+ "height": 17.70860035786427,
+ "seed": 1119741580,
+ "groupIds": [
+ "6TZ5hG1Ee6MddXwuJXdr5",
+ "xe_VmpQuCkK-2Lu9CGL8R"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 940,
+ "versionNonce": 1758560012,
+ "isDeleted": false,
+ "id": "B8bLOHZrRhslEVbBP9jp4",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1784.9536781277313,
+ "y": -1850.050828266343,
+ "strokeColor": "#669df6",
+ "backgroundColor": "#aecbfa",
+ "width": 8.85797007441361,
+ "height": 17.70860035786427,
+ "seed": 836318476,
+ "groupIds": [
+ "6DOjq1TwR59SoS2HlstbS",
+ "xe_VmpQuCkK-2Lu9CGL8R"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 982,
+ "versionNonce": 1716701108,
+ "isDeleted": false,
+ "id": "DWq3ahV34UH2RP3JIa7EI",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1794.1487120925142,
+ "y": -1850.1907276377208,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#669df6",
+ "width": 8.85797007441361,
+ "height": 17.70860035786427,
+ "seed": 1837544332,
+ "groupIds": [
+ "6DOjq1TwR59SoS2HlstbS",
+ "xe_VmpQuCkK-2Lu9CGL8R"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 940,
+ "versionNonce": 1927658892,
+ "isDeleted": false,
+ "id": "qDYX7fV1q3K5MDGAqCMtJ",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1785.4598412517405,
+ "y": -1806.9553369965429,
+ "strokeColor": "#669df6",
+ "backgroundColor": "#aecbfa",
+ "width": 8.85797007441361,
+ "height": 17.70860035786427,
+ "seed": 67133964,
+ "groupIds": [
+ "hdInUI-orAUWqQtInirJI",
+ "xe_VmpQuCkK-2Lu9CGL8R"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 982,
+ "versionNonce": 1703391540,
+ "isDeleted": false,
+ "id": "SeTrg7R5VpHNGmyPOfW-J",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1794.6548752165222,
+ "y": -1807.0952363679203,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#669df6",
+ "width": 8.85797007441361,
+ "height": 17.70860035786427,
+ "seed": 742408332,
+ "groupIds": [
+ "hdInUI-orAUWqQtInirJI",
+ "xe_VmpQuCkK-2Lu9CGL8R"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 944,
+ "versionNonce": 1559112716,
+ "isDeleted": false,
+ "id": "cYzBD-ipbA2Cd4ciFIXPC",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1742.5257427528738,
+ "y": -1807.5070327656533,
+ "strokeColor": "#669df6",
+ "backgroundColor": "#aecbfa",
+ "width": 8.85797007441361,
+ "height": 17.70860035786427,
+ "seed": 771772172,
+ "groupIds": [
+ "OYW2zQVmsyxVaA_wYND5b",
+ "xe_VmpQuCkK-2Lu9CGL8R"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 986,
+ "versionNonce": 1365469876,
+ "isDeleted": false,
+ "id": "Zi6tOTdx4Ut6ljQvl5B9v",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1751.7207767176571,
+ "y": -1807.6469321370298,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#669df6",
+ "width": 8.85797007441361,
+ "height": 17.70860035786427,
+ "seed": 1236962700,
+ "groupIds": [
+ "OYW2zQVmsyxVaA_wYND5b",
+ "xe_VmpQuCkK-2Lu9CGL8R"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "line",
+ "version": 659,
+ "versionNonce": 2028259980,
+ "isDeleted": false,
+ "id": "5ggsny_XDGStwZ4geHTfV",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2028.395096217105,
+ "y": -1979.2189212533528,
+ "strokeColor": "#000000",
+ "backgroundColor": "#4285f4",
+ "width": 7.100055542062618,
+ "height": 14.200096227246,
+ "seed": 1263001868,
+ "groupIds": [
+ "aKhaUT8I7Mv1hKXBftWpR",
+ "s71cWJFwFITMU-Lps6d9w",
+ "_AHsh0V9g7Gmkyg8iVyyC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -7.100055542062618,
+ 0
+ ],
+ [
+ -7.100055542062618,
+ -14.200096227246
+ ],
+ [
+ 0,
+ -14.200096227246
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 658,
+ "versionNonce": 210498612,
+ "isDeleted": false,
+ "id": "FoSfSZMYBdt05snHqeZzA",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1996.444869801216,
+ "y": -1979.2189212533528,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 56.80039233742361,
+ "height": 17.750125236350584,
+ "seed": 1010818956,
+ "groupIds": [
+ "8t5lPH-tVD4D3JjRJY2Da",
+ "s71cWJFwFITMU-Lps6d9w",
+ "_AHsh0V9g7Gmkyg8iVyyC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 17.750125236350584
+ ],
+ [
+ 7.100049351696269,
+ 17.750125236350584
+ ],
+ [
+ 7.100049351696269,
+ 7.10005554206262
+ ],
+ [
+ 24.850170873827043,
+ 7.10005554206262
+ ],
+ [
+ 24.850170873827043,
+ 17.750125236350584
+ ],
+ [
+ 31.950226415889656,
+ 17.750125236350584
+ ],
+ [
+ 31.950226415889656,
+ 7.10005554206262
+ ],
+ [
+ 49.70034917609371,
+ 7.10005554206262
+ ],
+ [
+ 49.70034917609371,
+ 17.750125236350584
+ ],
+ [
+ 56.80039233742361,
+ 17.750125236350584
+ ],
+ [
+ 56.80039233742361,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 658,
+ "versionNonce": 1233361164,
+ "isDeleted": false,
+ "id": "ypWdYRNc4UGk91mI9W6qc",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2003.544919152912,
+ "y": -2011.1691427169494,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 42.60029982439741,
+ "height": 17.750125236350584,
+ "seed": 2058620428,
+ "groupIds": [
+ "iivjceHzn0JoLGUUUq30p",
+ "s71cWJFwFITMU-Lps6d9w",
+ "_AHsh0V9g7Gmkyg8iVyyC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 42.60029982439741,
+ 0
+ ],
+ [
+ 42.60029982439741,
+ 17.750125236350584
+ ],
+ [
+ 0,
+ 17.750125236350584
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 658,
+ "versionNonce": 887573940,
+ "isDeleted": false,
+ "id": "S0OfeeLQomDTwrfbWdVPW",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2024.8450672080003,
+ "y": -2011.1691427169494,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 21.30015176930862,
+ "height": 17.750125236350584,
+ "seed": 1738435724,
+ "groupIds": [
+ "gxQeb-Dyeh10hmcLocvwG",
+ "s71cWJFwFITMU-Lps6d9w",
+ "_AHsh0V9g7Gmkyg8iVyyC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 21.30015176930862,
+ 0
+ ],
+ [
+ 21.30015176930862,
+ 17.750125236350584
+ ],
+ [
+ 0,
+ 17.750125236350584
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 658,
+ "versionNonce": 1782880140,
+ "isDeleted": false,
+ "id": "msPcz2lJcuW8pI52B7no2",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2039.045165911394,
+ "y": -1961.4687960170022,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 21.300149293162075,
+ "height": 21.300149293162075,
+ "seed": 884538124,
+ "groupIds": [
+ "OotFB7H6XWAQ1ekdcaEQw",
+ "s71cWJFwFITMU-Lps6d9w",
+ "_AHsh0V9g7Gmkyg8iVyyC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 21.300149293162075,
+ 0
+ ],
+ [
+ 21.300149293162075,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 658,
+ "versionNonce": 1000044340,
+ "isDeleted": false,
+ "id": "46ogLanYL-b5Vshd1BuQ_",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1989.3448192114458,
+ "y": -1961.4687960170022,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 21.300149293162075,
+ "height": 21.300149293162075,
+ "seed": 1283307916,
+ "groupIds": [
+ "OotFB7H6XWAQ1ekdcaEQw",
+ "s71cWJFwFITMU-Lps6d9w",
+ "_AHsh0V9g7Gmkyg8iVyyC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 21.300149293162075,
+ 0
+ ],
+ [
+ 21.300149293162075,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 658,
+ "versionNonce": 2002896396,
+ "isDeleted": false,
+ "id": "jOvslEebqD-I0qJnprYsh",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1999.9948950961002,
+ "y": -1961.4687960170022,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 10.650073408507769,
+ "height": 21.300149293162075,
+ "seed": 975611916,
+ "groupIds": [
+ "6wBq97BQG5vcRxZ7GY2Li",
+ "s71cWJFwFITMU-Lps6d9w",
+ "_AHsh0V9g7Gmkyg8iVyyC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 10.650073408507769,
+ 0
+ ],
+ [
+ 10.650073408507769,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 658,
+ "versionNonce": 2098098356,
+ "isDeleted": false,
+ "id": "aNxix0zmn9vAvQeeYHjXp",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2014.1949925614203,
+ "y": -1961.4687960170022,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 21.30015176930862,
+ "height": 21.300149293162075,
+ "seed": 296601228,
+ "groupIds": [
+ "PmWrgGGmRyNNh7W_7SpcI",
+ "s71cWJFwFITMU-Lps6d9w",
+ "_AHsh0V9g7Gmkyg8iVyyC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 21.30015176930862,
+ 0
+ ],
+ [
+ 21.30015176930862,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 658,
+ "versionNonce": 1976902796,
+ "isDeleted": false,
+ "id": "-gCeUl23RAdBaqghoMu75",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2024.8450672080003,
+ "y": -1961.4687960170022,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 10.650077122727579,
+ "height": 21.300149293162075,
+ "seed": 1473058060,
+ "groupIds": [
+ "KZ7foUUozdX0JleP4ZFjN",
+ "s71cWJFwFITMU-Lps6d9w",
+ "_AHsh0V9g7Gmkyg8iVyyC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 10.650077122727579,
+ 0
+ ],
+ [
+ 10.650077122727579,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 658,
+ "versionNonce": 1491483188,
+ "isDeleted": false,
+ "id": "UuIl_4Ro0nQeczSgTWIqF",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2049.695245510268,
+ "y": -1961.4687960170022,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 10.650069694287957,
+ "height": 21.300149293162075,
+ "seed": 1778291596,
+ "groupIds": [
+ "KZ7foUUozdX0JleP4ZFjN",
+ "s71cWJFwFITMU-Lps6d9w",
+ "_AHsh0V9g7Gmkyg8iVyyC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 10.650069694287957,
+ 0
+ ],
+ [
+ 10.650069694287957,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1380,
+ "versionNonce": 995668748,
+ "isDeleted": false,
+ "id": "CyL4xr_Q5hzWp5O3epy6O",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1975.1627292376666,
+ "y": -1937.3655267831248,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 99.56015014648438,
+ "height": 27.44457849673917,
+ "seed": 1719419404,
+ "groupIds": [
+ "vBFiVSGUjnxeTQibkcWbp",
+ "2Xg7V_RgexkPamV5bhliA",
+ "Jy4_A0VUJJS52Onc5RubG",
+ "keZ69FOJuasvauJpOdukW",
+ "1whHlGRkzVeXj0v3-syM0",
+ "GwTn6jd8QGpnJKyf_0Ggi",
+ "_AHsh0V9g7Gmkyg8iVyyC"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "fontSize": 11.942915477701884,
+ "fontFamily": 2,
+ "text": "Global Application \nLoad Balancer",
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Global Application \nLoad Balancer",
+ "lineHeight": 1.1489898989898986,
+ "baseline": 24
+ },
+ {
+ "type": "rectangle",
+ "version": 571,
+ "versionNonce": 1239784204,
+ "isDeleted": false,
+ "id": "vGubQl3uFUNUf5EgTMZxr",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2460.4412056033975,
+ "y": -1998.999805226251,
+ "strokeColor": "#343a40",
+ "backgroundColor": "#ced4da",
+ "width": 58.14103866046347,
+ "height": 38.76069244030904,
+ "seed": 484798004,
+ "groupIds": [
+ "AgHaNJVHW2KnAgQ7rVvSW"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 1
+ },
+ "boundElements": [],
+ "updated": 1693311622859,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 682,
+ "versionNonce": 1696492468,
+ "isDeleted": false,
+ "id": "UboAAINFFvnut6GEQ5Qf0",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2454.8958967490294,
+ "y": -1955.924839909427,
+ "strokeColor": "#343a40",
+ "backgroundColor": "#ced4da",
+ "width": 68.31684981684984,
+ "height": 9.351355868465966,
+ "seed": 805406644,
+ "groupIds": [
+ "AgHaNJVHW2KnAgQ7rVvSW"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 1
+ },
+ "boundElements": [],
+ "updated": 1693311622859,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 851,
+ "versionNonce": 1393973644,
+ "isDeleted": false,
+ "id": "fgmq6SWA3TxH_OK31cxRI",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2505.263704355122,
+ "y": -1952.500399732242,
+ "strokeColor": "#343a40",
+ "backgroundColor": "#343a40",
+ "width": 12.241641915449078,
+ "height": 2.3597905067140177,
+ "seed": 190738740,
+ "groupIds": [
+ "AgHaNJVHW2KnAgQ7rVvSW"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 1
+ },
+ "boundElements": [],
+ "updated": 1693311622859,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 953,
+ "versionNonce": 1396626740,
+ "isDeleted": false,
+ "id": "zfNuhr69KIBjKjVv3Te23",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2483.575583232952,
+ "y": -1959.6346501013777,
+ "strokeColor": "#343a40",
+ "backgroundColor": "#343a40",
+ "width": 12.241641915449078,
+ "height": 2.3597905067140177,
+ "seed": 241314484,
+ "groupIds": [
+ "AgHaNJVHW2KnAgQ7rVvSW"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 1
+ },
+ "boundElements": [],
+ "updated": 1693311622859,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 1280,
+ "versionNonce": 615204876,
+ "isDeleted": false,
+ "id": "m4iruD5GCZYHmsh6ARTfN",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2465.434373252083,
+ "y": -1994.5155791557954,
+ "strokeColor": "#343a40",
+ "backgroundColor": "#343a40",
+ "width": 48.22892577732466,
+ "height": 30.250725686721108,
+ "seed": 1001567284,
+ "groupIds": [
+ "AgHaNJVHW2KnAgQ7rVvSW"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 1
+ },
+ "boundElements": [
+ {
+ "id": "TAuiOhdXL8nMhW4lSzGVF",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1693311622859,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "arrow",
+ "version": 393,
+ "versionNonce": 1150684852,
+ "isDeleted": false,
+ "id": "TAuiOhdXL8nMhW4lSzGVF",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2449.6334814011047,
+ "y": -1973.621191427633,
+ "strokeColor": "#495057",
+ "backgroundColor": "#a5d8ff",
+ "width": 367.16286324641396,
+ "height": 1.267797616311782,
+ "seed": 540427276,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1693311622859,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "m4iruD5GCZYHmsh6ARTfN",
+ "focus": -0.37026341157566467,
+ "gap": 15.800891850978132
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -367.16286324641396,
+ 1.267797616311782
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 619,
+ "versionNonce": 545017524,
+ "isDeleted": false,
+ "id": "jld-qaYp-lDgq1Hm1GEN6",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2449.642702346363,
+ "y": -1700.1766895233552,
+ "strokeColor": "#495057",
+ "backgroundColor": "#a5d8ff",
+ "width": 368.65484385066657,
+ "height": 0.699874537994674,
+ "seed": 992116916,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1693311370548,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "oDtXSp8ctOQP7XMgXUvae",
+ "focus": 4.148172941424359,
+ "gap": 14.90177671033041
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -368.65484385066657,
+ 0.699874537994674
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 1061,
+ "versionNonce": 349228300,
+ "isDeleted": false,
+ "id": "R5B-Lf8AWDyW_GkYfTlqF",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2027.5186511344352,
+ "y": -1712.8244280181282,
+ "strokeColor": "#000000",
+ "backgroundColor": "#4285f4",
+ "width": 7.100055542062618,
+ "height": 14.200096227246,
+ "seed": 851713204,
+ "groupIds": [
+ "CQ1bM3lzktqbLiEs8kcm9",
+ "GBOtEdRzL8dZfz5OAKnkX",
+ "0mZoIb0G3Op6Tv2Ek2tUm"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -7.100055542062618,
+ 0
+ ],
+ [
+ -7.100055542062618,
+ -14.200096227246
+ ],
+ [
+ 0,
+ -14.200096227246
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 1060,
+ "versionNonce": 2000116148,
+ "isDeleted": false,
+ "id": "75HIMaRCT06X2FxFa6xIo",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1995.568424718546,
+ "y": -1712.8244280181282,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 56.80039233742361,
+ "height": 17.750125236350584,
+ "seed": 587621940,
+ "groupIds": [
+ "YbcJIKMp3pnjXFjw5gmjG",
+ "GBOtEdRzL8dZfz5OAKnkX",
+ "0mZoIb0G3Op6Tv2Ek2tUm"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 17.750125236350584
+ ],
+ [
+ 7.100049351696269,
+ 17.750125236350584
+ ],
+ [
+ 7.100049351696269,
+ 7.10005554206262
+ ],
+ [
+ 24.850170873827043,
+ 7.10005554206262
+ ],
+ [
+ 24.850170873827043,
+ 17.750125236350584
+ ],
+ [
+ 31.950226415889656,
+ 17.750125236350584
+ ],
+ [
+ 31.950226415889656,
+ 7.10005554206262
+ ],
+ [
+ 49.70034917609371,
+ 7.10005554206262
+ ],
+ [
+ 49.70034917609371,
+ 17.750125236350584
+ ],
+ [
+ 56.80039233742361,
+ 17.750125236350584
+ ],
+ [
+ 56.80039233742361,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 1060,
+ "versionNonce": 1036733324,
+ "isDeleted": false,
+ "id": "iygoDAQ2e2pWIXqjFLgtr",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2002.668474070242,
+ "y": -1744.774649481725,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 42.60029982439741,
+ "height": 17.750125236350584,
+ "seed": 927170484,
+ "groupIds": [
+ "oTK-E8aRZNeeuwG7ncCQb",
+ "GBOtEdRzL8dZfz5OAKnkX",
+ "0mZoIb0G3Op6Tv2Ek2tUm"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 42.60029982439741,
+ 0
+ ],
+ [
+ 42.60029982439741,
+ 17.750125236350584
+ ],
+ [
+ 0,
+ 17.750125236350584
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 1060,
+ "versionNonce": 1342833460,
+ "isDeleted": false,
+ "id": "STTal_nPnwwcdnULvH1s_",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2023.9686221253303,
+ "y": -1744.774649481725,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 21.30015176930862,
+ "height": 17.750125236350584,
+ "seed": 1201760564,
+ "groupIds": [
+ "gCinajIwnuUWSBHE-sQcZ",
+ "GBOtEdRzL8dZfz5OAKnkX",
+ "0mZoIb0G3Op6Tv2Ek2tUm"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 21.30015176930862,
+ 0
+ ],
+ [
+ 21.30015176930862,
+ 17.750125236350584
+ ],
+ [
+ 0,
+ 17.750125236350584
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 1060,
+ "versionNonce": 267516428,
+ "isDeleted": false,
+ "id": "dxF-hE2_N_vU2cU-pBAxQ",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2038.1687208287235,
+ "y": -1695.0743027817778,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 21.300149293162075,
+ "height": 21.300149293162075,
+ "seed": 931537588,
+ "groupIds": [
+ "Gh2KqpIV1BDMOrlVjRw0t",
+ "GBOtEdRzL8dZfz5OAKnkX",
+ "0mZoIb0G3Op6Tv2Ek2tUm"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 21.300149293162075,
+ 0
+ ],
+ [
+ 21.300149293162075,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 1060,
+ "versionNonce": 466320564,
+ "isDeleted": false,
+ "id": "g5zqGfYvXPKxPuBkOysUV",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1988.468374128776,
+ "y": -1695.0743027817778,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 21.300149293162075,
+ "height": 21.300149293162075,
+ "seed": 228043828,
+ "groupIds": [
+ "Gh2KqpIV1BDMOrlVjRw0t",
+ "GBOtEdRzL8dZfz5OAKnkX",
+ "0mZoIb0G3Op6Tv2Ek2tUm"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 21.300149293162075,
+ 0
+ ],
+ [
+ 21.300149293162075,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 1060,
+ "versionNonce": 1983830156,
+ "isDeleted": false,
+ "id": "2uQ9yIYgEOGIY0aMPaISL",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1999.1184500134302,
+ "y": -1695.0743027817778,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 10.650073408507769,
+ "height": 21.300149293162075,
+ "seed": 196871604,
+ "groupIds": [
+ "WYbK7bfNXbkEfUGJfZMQq",
+ "GBOtEdRzL8dZfz5OAKnkX",
+ "0mZoIb0G3Op6Tv2Ek2tUm"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 10.650073408507769,
+ 0
+ ],
+ [
+ 10.650073408507769,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 1060,
+ "versionNonce": 874628660,
+ "isDeleted": false,
+ "id": "hTgnL6v7qkAPTso2cg7Cc",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2013.3185474787504,
+ "y": -1695.0743027817778,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 21.30015176930862,
+ "height": 21.300149293162075,
+ "seed": 1192832820,
+ "groupIds": [
+ "Hu7ZxdfF0OFO-s4CEPgpT",
+ "GBOtEdRzL8dZfz5OAKnkX",
+ "0mZoIb0G3Op6Tv2Ek2tUm"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 21.30015176930862,
+ 0
+ ],
+ [
+ 21.30015176930862,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 1060,
+ "versionNonce": 1819010828,
+ "isDeleted": false,
+ "id": "K3s1Vnsu7Ig84d8WlyZLN",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2023.9686221253303,
+ "y": -1695.0743027817778,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 10.650077122727579,
+ "height": 21.300149293162075,
+ "seed": 1075128500,
+ "groupIds": [
+ "DtinCYcF5v_IxZzOSB7KJ",
+ "GBOtEdRzL8dZfz5OAKnkX",
+ "0mZoIb0G3Op6Tv2Ek2tUm"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 10.650077122727579,
+ 0
+ ],
+ [
+ 10.650077122727579,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 1060,
+ "versionNonce": 1307762612,
+ "isDeleted": false,
+ "id": "ddCzKIj9MKTWcEJNDkTPP",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2048.8188004275976,
+ "y": -1695.0743027817778,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 10.650069694287957,
+ "height": 21.300149293162075,
+ "seed": 2052452916,
+ "groupIds": [
+ "DtinCYcF5v_IxZzOSB7KJ",
+ "GBOtEdRzL8dZfz5OAKnkX",
+ "0mZoIb0G3Op6Tv2Ek2tUm"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 10.650069694287957,
+ 0
+ ],
+ [
+ 10.650069694287957,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 21.300149293162075
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1790,
+ "versionNonce": 1727210892,
+ "isDeleted": false,
+ "id": "NdTyUMyVg94zhiQoU_aYH",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1971.6306902829263,
+ "y": -1670.9710335479006,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 104.871337890625,
+ "height": 27.44457849673917,
+ "seed": 348228532,
+ "groupIds": [
+ "41ylW99tmvbdcONHuTjBJ",
+ "6SNEKlxIFFvifHmQdQfYo",
+ "l_CRgPkxtok1To7KswRaa",
+ "DRfmOGZAtVLdORn2xR7yn",
+ "itc1FZ0Xs2C1kbMujZH3W",
+ "poY-lWcY20jfnarj8L3hI",
+ "0mZoIb0G3Op6Tv2Ek2tUm"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1693311295407,
+ "link": null,
+ "locked": false,
+ "fontSize": 11.942915477701884,
+ "fontFamily": 2,
+ "text": "Internal Application \nLoad Balancer",
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Internal Application \nLoad Balancer",
+ "lineHeight": 1.1489898989898986,
+ "baseline": 24
+ },
+ {
+ "type": "line",
+ "version": 311,
+ "versionNonce": 754670260,
+ "isDeleted": false,
+ "id": "il4-XEVmby3tUIrvWychm",
+ "fillStyle": "hachure",
+ "strokeWidth": 4,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2135.7533145705465,
+ "y": -1723.4518645312019,
+ "strokeColor": "#495057",
+ "backgroundColor": "#a5d8ff",
+ "width": 229.85181919907745,
+ "height": 1.5896375165302743,
+ "seed": 1306986508,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1693311374017,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 229.85181919907745,
+ 1.5896375165302743
+ ]
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 820,
+ "versionNonce": 1107363852,
+ "isDeleted": false,
+ "id": "6D4oJaBAuLoDYS0zF_CWc",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2366.761316286691,
+ "y": -1810.3894047977235,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e9ecef",
+ "width": 267.30751899828243,
+ "height": 204.09209589366222,
+ "seed": 1790217356,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [],
+ "updated": 1693311370548,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 106,
+ "versionNonce": 999402420,
+ "isDeleted": false,
+ "id": "aOb2ctCdAG_zpbjcLInk7",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2386.830190811656,
+ "y": -1791.7940740188417,
+ "strokeColor": "#495057",
+ "backgroundColor": "#a5d8ff",
+ "width": 113.572265625,
+ "height": 32.199999999999996,
+ "seed": 5710604,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311325062,
+ "link": null,
+ "locked": false,
+ "fontSize": 28,
+ "fontFamily": 2,
+ "text": "On-Prem",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "On-Prem",
+ "lineHeight": 1.15,
+ "baseline": 26
+ },
+ {
+ "type": "rectangle",
+ "version": 498,
+ "versionNonce": 651282572,
+ "isDeleted": false,
+ "id": "8prp-Y-70ERWfgc7aC2Go",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2466.891987197782,
+ "y": -1728.3498781298488,
+ "strokeColor": "#343a40",
+ "backgroundColor": "#ced4da",
+ "width": 58.14103866046347,
+ "height": 38.76069244030904,
+ "seed": 1368666548,
+ "groupIds": [
+ "fva9Mw0UhifIbppAvrWKB"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 1
+ },
+ "boundElements": [],
+ "updated": 1693311341279,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 610,
+ "versionNonce": 852047372,
+ "isDeleted": false,
+ "id": "oDtXSp8ctOQP7XMgXUvae",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2461.3466783434137,
+ "y": -1685.2749128130247,
+ "strokeColor": "#343a40",
+ "backgroundColor": "#ced4da",
+ "width": 68.31684981684984,
+ "height": 9.351355868465966,
+ "seed": 2030820148,
+ "groupIds": [
+ "fva9Mw0UhifIbppAvrWKB"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 1
+ },
+ "boundElements": [
+ {
+ "id": "jld-qaYp-lDgq1Hm1GEN6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1693311367412,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 778,
+ "versionNonce": 1830868748,
+ "isDeleted": false,
+ "id": "K5WdDbm7qlHVQ9sg0unq-",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2511.714485949507,
+ "y": -1681.8504726358399,
+ "strokeColor": "#343a40",
+ "backgroundColor": "#343a40",
+ "width": 12.241641915449078,
+ "height": 2.3597905067140177,
+ "seed": 1993495732,
+ "groupIds": [
+ "fva9Mw0UhifIbppAvrWKB"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 1
+ },
+ "boundElements": [],
+ "updated": 1693311341279,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 880,
+ "versionNonce": 323284916,
+ "isDeleted": false,
+ "id": "itEeNm3te2cPocf6QqiJM",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2490.0263648273362,
+ "y": -1688.9847230049756,
+ "strokeColor": "#343a40",
+ "backgroundColor": "#343a40",
+ "width": 12.241641915449078,
+ "height": 2.3597905067140177,
+ "seed": 948805172,
+ "groupIds": [
+ "fva9Mw0UhifIbppAvrWKB"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 1
+ },
+ "boundElements": [],
+ "updated": 1693311341279,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 1207,
+ "versionNonce": 1669993868,
+ "isDeleted": false,
+ "id": "qehCG6gKwZ0RnSR7ipnqd",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2471.8851548464677,
+ "y": -1723.8656520593934,
+ "strokeColor": "#343a40",
+ "backgroundColor": "#343a40",
+ "width": 48.22892577732466,
+ "height": 30.250725686721108,
+ "seed": 119781300,
+ "groupIds": [
+ "fva9Mw0UhifIbppAvrWKB"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 1
+ },
+ "boundElements": [],
+ "updated": 1693311341279,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 33,
+ "versionNonce": 1139228212,
+ "isDeleted": false,
+ "id": "xCVELrM_0z2Bt7m7t6pMl",
+ "fillStyle": "hachure",
+ "strokeWidth": 4,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2197.826319894205,
+ "y": -1758.9976717266175,
+ "strokeColor": "#495057",
+ "backgroundColor": "#e9ecef",
+ "width": 156.748046875,
+ "height": 23,
+ "seed": 262530100,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311490560,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 2,
+ "text": "VPN/Interconnect",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VPN/Interconnect",
+ "lineHeight": 1.15,
+ "baseline": 19
+ },
+ {
+ "type": "line",
+ "version": 187,
+ "versionNonce": 1239730612,
+ "isDeleted": false,
+ "id": "n_lhRcvZtm3yWi3zNEBJV",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1964.9459496117454,
+ "y": -1958.8875906849437,
+ "strokeColor": "#1971c2",
+ "backgroundColor": "#e9ecef",
+ "width": 126.17815604760767,
+ "height": 1.1773083313553343,
+ "seed": 1396605748,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1693311460507,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -126.17815604760767,
+ 1.1773083313553343
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 306,
+ "versionNonce": 1257036684,
+ "isDeleted": false,
+ "id": "4L4IgDappf0lRmyc8X10d",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1961.783334454876,
+ "y": -1710.9000432513847,
+ "strokeColor": "#1971c2",
+ "backgroundColor": "#e9ecef",
+ "width": 122.06028957987155,
+ "height": 243.10060696483674,
+ "seed": 913199116,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1693311460507,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -122.06028957987155,
+ -243.10060696483674
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 208,
+ "versionNonce": 2022298036,
+ "isDeleted": false,
+ "id": "1hR9SCYu3Fd8-OWyibUBe",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1772.833101624224,
+ "y": -1902.1597754192694,
+ "strokeColor": "#1971c2",
+ "backgroundColor": "#e9ecef",
+ "width": 0.6456206978398313,
+ "height": 155.9743650605269,
+ "seed": 69677324,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1693311488870,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "IfVzFaOMx3j3dME8GrhUs",
+ "focus": 0.15801185672017465,
+ "gap": 8.268924980427641
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0.6456206978398313,
+ 155.9743650605269
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 304,
+ "versionNonce": 594979252,
+ "isDeleted": false,
+ "id": "n7ln0pmmtpM6jtsJAZLmo",
+ "fillStyle": "hachure",
+ "strokeWidth": 4,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1617.267374474415,
+ "y": -1860.5351407849744,
+ "strokeColor": "#495057",
+ "backgroundColor": "#e9ecef",
+ "width": 117.3671875,
+ "height": 36.8,
+ "seed": 910776244,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311514550,
+ "link": null,
+ "locked": false,
+ "fontSize": 16,
+ "fontFamily": 2,
+ "text": "VPC Serverless \nConnector",
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VPC Serverless \nConnector",
+ "lineHeight": 1.15,
+ "baseline": 33
+ },
+ {
+ "type": "line",
+ "version": 337,
+ "versionNonce": 1336321716,
+ "isDeleted": false,
+ "id": "R-U4aOs1dKPpH1p-kUXVj",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2194.3454487369822,
+ "y": -2008.1902074796785,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 53.252513658530155,
+ "height": 66.25938635522165,
+ "seed": 1866338828,
+ "groupIds": [
+ "ng1U6l9sAI234KMxvTZ2o",
+ "LA_S6hEfLFuoibT7K3arg",
+ "omvCuEw-yZaaMA9sUJfIC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311607367,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 26.626256829265078,
+ 11.78184860439602
+ ],
+ [
+ 26.626256829265078,
+ 29.652788806337103
+ ],
+ [
+ 26.626256829265078,
+ 29.652788806337103
+ ],
+ [
+ 26.604619250085825,
+ 31.214368306506216
+ ],
+ [
+ 26.52013407235635,
+ 32.76351547468527
+ ],
+ [
+ 26.373875363635207,
+ 34.29878764285813
+ ],
+ [
+ 26.166873252353522,
+ 35.818725055570226
+ ],
+ [
+ 25.900201806069838,
+ 37.32187772161756
+ ],
+ [
+ 25.574910681716354,
+ 38.806795649796086
+ ],
+ [
+ 25.192054418350562,
+ 40.272023966776544
+ ],
+ [
+ 24.752692437155186,
+ 41.71612244560541
+ ],
+ [
+ 24.257874395062434,
+ 43.13762644870285
+ ],
+ [
+ 23.708659713255045,
+ 44.535090866990146
+ ],
+ [
+ 23.10610781291576,
+ 45.90706570926324
+ ],
+ [
+ 22.451263468851515,
+ 47.252091220067584
+ ],
+ [
+ 21.745200748620857,
+ 48.56872717244969
+ ],
+ [
+ 20.98894978065492,
+ 49.855513810955
+ ],
+ [
+ 20.183584632512247,
+ 51.11100602650476
+ ],
+ [
+ 19.330159843250307,
+ 52.33374894576966
+ ],
+ [
+ 18.4297250698013,
+ 53.52228769542044
+ ],
+ [
+ 17.48333485122271,
+ 54.675177166378305
+ ],
+ [
+ 16.492048608697264,
+ 55.790962485313976
+ ],
+ [
+ 15.456920881282457,
+ 56.868193661023454
+ ],
+ [
+ 14.379006208035735,
+ 57.9054158201774
+ ],
+ [
+ 13.259368892265126,
+ 58.90118385369711
+ ],
+ [
+ 12.09904882665229,
+ 59.85404288825326
+ ],
+ [
+ 10.89911031450523,
+ 60.76254293264181
+ ],
+ [
+ 9.66060789488143,
+ 61.6252291135335
+ ],
+ [
+ 8.384600988963609,
+ 62.44065143972429
+ ],
+ [
+ 7.072144135809265,
+ 63.20735992001017
+ ],
+ [
+ 5.724286992350592,
+ 63.9239045631871
+ ],
+ [
+ 4.342088979770334,
+ 64.58883049592579
+ ],
+ [
+ 2.926599755000689,
+ 65.20068772702223
+ ],
+ [
+ 1.4788885034749268,
+ 65.7580213831471
+ ],
+ [
+ 0,
+ 66.25938635522165
+ ],
+ [
+ -1.477797348477959,
+ 65.7591882110857
+ ],
+ [
+ -2.9244906768857386,
+ 65.20307508627732
+ ],
+ [
+ -4.339025446165864,
+ 64.59250185412587
+ ],
+ [
+ -5.720344676198234,
+ 63.92892338796062
+ ],
+ [
+ -7.067391386862741,
+ 63.21377991473506
+ ],
+ [
+ -8.379113480164545,
+ 62.44851654352796
+ ],
+ [
+ -9.654453975983534,
+ 61.63458326554331
+ ],
+ [
+ -10.892353453136977,
+ 60.77343007198517
+ ],
+ [
+ -12.091764695755302,
+ 59.86649718980701
+ ],
+ [
+ -13.251623400530494,
+ 58.91522972808762
+ ],
+ [
+ -14.370879910530354,
+ 57.92108744228152
+ ],
+ [
+ -15.448477245634779,
+ 56.88551055934222
+ ],
+ [
+ -16.48335598466102,
+ 55.809934424097946
+ ],
+ [
+ -17.47446647067688,
+ 54.695823674128555
+ ],
+ [
+ -18.420749282499617,
+ 53.54461853638747
+ ],
+ [
+ -19.32114988107176,
+ 52.35776411995351
+ ],
+ [
+ -20.17461372733583,
+ 51.13671529815592
+ ],
+ [
+ -20.980083841171723,
+ 49.88290741582297
+ ],
+ [
+ -21.736505683521976,
+ 48.59779534628392
+ ],
+ [
+ -22.442822274266465,
+ 47.28281931649227
+ ],
+ [
+ -23.097979074347734,
+ 45.93943419977733
+ ],
+ [
+ -23.700921544708308,
+ 44.56908510521784
+ ],
+ [
+ -24.250591484696756,
+ 43.17322202401783
+ ],
+ [
+ -24.74593557578693,
+ 41.75327541888024
+ ],
+ [
+ -25.185895617327397,
+ 40.310709927384934
+ ],
+ [
+ -25.569419511323336,
+ 38.84697065861065
+ ],
+ [
+ -25.89545149818594,
+ 37.36349783951088
+ ],
+ [
+ -26.162932156732477,
+ 35.8617463434149
+ ],
+ [
+ -26.3708093889681,
+ 34.34315151515097
+ ],
+ [
+ -26.51802621477272,
+ 32.80916822804836
+ ],
+ [
+ -26.603526874557538,
+ 31.261246473311093
+ ],
+ [
+ -26.626256829265078,
+ 29.70082647789267
+ ],
+ [
+ -26.626256829265078,
+ 11.865918801477871
+ ],
+ [
+ 0,
+ 0.08407080734751268
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 337,
+ "versionNonce": 1619218060,
+ "isDeleted": false,
+ "id": "n0rqf1bsn46wLsUI73IjO",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2194.3454487369822,
+ "y": -2014.5074880864959,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 64.79416597169477,
+ "height": 78.53365069094667,
+ "seed": 1145565324,
+ "groupIds": [
+ "ng1U6l9sAI234KMxvTZ2o",
+ "LA_S6hEfLFuoibT7K3arg",
+ "omvCuEw-yZaaMA9sUJfIC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311607367,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -32.3910758358403,
+ 14.448078211167381
+ ],
+ [
+ -32.3910758358403,
+ 36.00609705655551
+ ],
+ [
+ -32.3910758358403,
+ 36.00609705655551
+ ],
+ [
+ -32.367369456079544,
+ 37.84777384749002
+ ],
+ [
+ -32.2678552659841,
+ 39.674028004708994
+ ],
+ [
+ -32.093853880438466,
+ 41.48310440417927
+ ],
+ [
+ -31.84668286299882,
+ 43.27322839336664
+ ],
+ [
+ -31.527663438815274,
+ 45.04263752505005
+ ],
+ [
+ -31.138114391975353,
+ 46.78956446988316
+ ],
+ [
+ -30.67935450656653,
+ 48.51224189851966
+ ],
+ [
+ -30.15270500773895,
+ 50.20890736373851
+ ],
+ [
+ -29.559482848783137,
+ 51.87779841831865
+ ],
+ [
+ -28.90100986511489,
+ 53.517137968663214
+ ],
+ [
+ -28.17860423055603,
+ 55.12516844967642
+ ],
+ [
+ -27.393585339459733,
+ 56.70012253201196
+ ],
+ [
+ -26.54727380671047,
+ 58.24023776844875
+ ],
+ [
+ -25.64098780613007,
+ 59.74373706538998
+ ],
+ [
+ -24.676047952603025,
+ 61.208867739865106
+ ],
+ [
+ -23.65377241995116,
+ 62.63385758040253
+ ],
+ [
+ -22.575480602527637,
+ 64.01693925765596
+ ],
+ [
+ -21.44249311521693,
+ 65.35635032440432
+ ],
+ [
+ -20.25613057290352,
+ 66.65032345130129
+ ],
+ [
+ -19.017711149409237,
+ 67.89709130900057
+ ],
+ [
+ -17.728551798024615,
+ 69.0948914502811
+ ],
+ [
+ -16.389973133634122,
+ 70.2419614279218
+ ],
+ [
+ -15.003298212184866,
+ 71.33651926620058
+ ],
+ [
+ -13.569842766436057,
+ 72.37681228214687
+ ],
+ [
+ -12.090927411272164,
+ 73.36107314641444
+ ],
+ [
+ -10.567872761577664,
+ 74.28753941178218
+ ],
+ [
+ -9.001996991174408,
+ 75.15443398465321
+ ],
+ [
+ -7.394618273884215,
+ 75.96000418205706
+ ],
+ [
+ -5.747059665654212,
+ 76.70247291039686
+ ],
+ [
+ -4.060636899243595,
+ 77.38008260457683
+ ],
+ [
+ -2.336670589536845,
+ 77.99106593525062
+ ],
+ [
+ -0.5764813514184316,
+ 78.53365069094667
+ ],
+ [
+ 0.6005014077275319,
+ 78.53365069094667
+ ],
+ [
+ 0.6005014077275319,
+ 78.53365069094667
+ ],
+ [
+ 2.353018385990306,
+ 77.99457618331694
+ ],
+ [
+ 4.06974450392746,
+ 77.38739114610014
+ ],
+ [
+ 5.749351823466625,
+ 76.71383361589105
+ ],
+ [
+ 7.3905368171617685,
+ 75.97567580416117
+ ],
+ [
+ 8.991981311191044,
+ 75.17466062963058
+ ],
+ [
+ 10.552362249607338,
+ 74.31256030377082
+ ],
+ [
+ 12.070371222839338,
+ 73.39111286317662
+ ],
+ [
+ 13.544694939190483,
+ 72.41209540144482
+ ],
+ [
+ 14.974020106964192,
+ 71.37725571942072
+ ],
+ [
+ 16.357023670213366,
+ 70.28834650007481
+ ],
+ [
+ 17.692392337241404,
+ 69.14713019062812
+ ],
+ [
+ 18.978817698477027,
+ 67.9553594740512
+ ],
+ [
+ 20.21498158009839,
+ 66.7148016796904
+ ],
+ [
+ 21.399565808283615,
+ 65.42720460839091
+ ],
+ [
+ 22.531252209210923,
+ 64.09432582524855
+ ],
+ [
+ 23.608732373308964,
+ 62.71793265960966
+ ],
+ [
+ 24.630693008881156,
+ 61.29976314806893
+ ],
+ [
+ 25.595815942105688,
+ 59.841589502097975
+ ],
+ [
+ 26.502782999160708,
+ 58.3451644046673
+ ],
+ [
+ 27.350280888349634,
+ 56.81225030299795
+ ],
+ [
+ 28.137006082226424,
+ 55.244594997935216
+ ],
+ [
+ 28.861625760593434,
+ 53.643960936700104
+ ],
+ [
+ 29.522826631754107,
+ 52.01210080226318
+ ],
+ [
+ 30.11930516826239,
+ 50.35077704184546
+ ],
+ [
+ 30.649738314171188,
+ 48.661747220542765
+ ],
+ [
+ 31.112807895658626,
+ 46.946759139200324
+ ],
+ [
+ 31.507210385278697,
+ 45.20758012716447
+ ],
+ [
+ 31.831622727084287,
+ 43.44596774953099
+ ],
+ [
+ 32.08473162937882,
+ 41.663669807145155
+ ],
+ [
+ 32.26521891834042,
+ 39.86244874722799
+ ],
+ [
+ 32.371781066523106,
+ 38.04406457593793
+ ],
+ [
+ 32.40309013585447,
+ 36.210269976245506
+ ],
+ [
+ 32.40309013585447,
+ 14.448078211167381
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 337,
+ "versionNonce": 1460121652,
+ "isDeleted": false,
+ "id": "peJDVA9mTcOvWBbtedtk9",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2194.3454487369822,
+ "y": -2008.1902074796785,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fff",
+ "width": 53.252513658530155,
+ "height": 66.25938635522165,
+ "seed": 1234304780,
+ "groupIds": [
+ "JHrgg4sS7bNU14okiLtbP",
+ "LA_S6hEfLFuoibT7K3arg",
+ "omvCuEw-yZaaMA9sUJfIC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311607367,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 26.626256829265078,
+ 11.78184860439602
+ ],
+ [
+ 26.626256829265078,
+ 29.652788806337103
+ ],
+ [
+ 26.626256829265078,
+ 29.652788806337103
+ ],
+ [
+ 26.604619250085825,
+ 31.214368306506216
+ ],
+ [
+ 26.52013407235635,
+ 32.76351547468527
+ ],
+ [
+ 26.373875363635207,
+ 34.29878764285813
+ ],
+ [
+ 26.166873252353522,
+ 35.818725055570226
+ ],
+ [
+ 25.900201806069838,
+ 37.32187772161756
+ ],
+ [
+ 25.574910681716354,
+ 38.806795649796086
+ ],
+ [
+ 25.192054418350562,
+ 40.272023966776544
+ ],
+ [
+ 24.752692437155186,
+ 41.71612244560541
+ ],
+ [
+ 24.257874395062434,
+ 43.13762644870285
+ ],
+ [
+ 23.708659713255045,
+ 44.535090866990146
+ ],
+ [
+ 23.10610781291576,
+ 45.90706570926324
+ ],
+ [
+ 22.451263468851515,
+ 47.252091220067584
+ ],
+ [
+ 21.745200748620857,
+ 48.56872717244969
+ ],
+ [
+ 20.98894978065492,
+ 49.855513810955
+ ],
+ [
+ 20.183584632512247,
+ 51.11100602650476
+ ],
+ [
+ 19.330159843250307,
+ 52.33374894576966
+ ],
+ [
+ 18.4297250698013,
+ 53.52228769542044
+ ],
+ [
+ 17.48333485122271,
+ 54.675177166378305
+ ],
+ [
+ 16.492048608697264,
+ 55.790962485313976
+ ],
+ [
+ 15.456920881282457,
+ 56.868193661023454
+ ],
+ [
+ 14.379006208035735,
+ 57.9054158201774
+ ],
+ [
+ 13.259368892265126,
+ 58.90118385369711
+ ],
+ [
+ 12.09904882665229,
+ 59.85404288825326
+ ],
+ [
+ 10.89911031450523,
+ 60.76254293264181
+ ],
+ [
+ 9.66060789488143,
+ 61.6252291135335
+ ],
+ [
+ 8.384600988963609,
+ 62.44065143972429
+ ],
+ [
+ 7.072144135809265,
+ 63.20735992001017
+ ],
+ [
+ 5.724286992350592,
+ 63.9239045631871
+ ],
+ [
+ 4.342088979770334,
+ 64.58883049592579
+ ],
+ [
+ 2.926599755000689,
+ 65.20068772702223
+ ],
+ [
+ 1.4788885034749268,
+ 65.7580213831471
+ ],
+ [
+ 0,
+ 66.25938635522165
+ ],
+ [
+ -1.477797348477959,
+ 65.7591882110857
+ ],
+ [
+ -2.9244906768857386,
+ 65.20307508627732
+ ],
+ [
+ -4.339025446165864,
+ 64.59250185412587
+ ],
+ [
+ -5.720344676198234,
+ 63.92892338796062
+ ],
+ [
+ -7.067391386862741,
+ 63.21377991473506
+ ],
+ [
+ -8.379113480164545,
+ 62.44851654352796
+ ],
+ [
+ -9.654453975983534,
+ 61.63458326554331
+ ],
+ [
+ -10.892353453136977,
+ 60.77343007198517
+ ],
+ [
+ -12.091764695755302,
+ 59.86649718980701
+ ],
+ [
+ -13.251623400530494,
+ 58.91522972808762
+ ],
+ [
+ -14.370879910530354,
+ 57.92108744228152
+ ],
+ [
+ -15.448477245634779,
+ 56.88551055934222
+ ],
+ [
+ -16.48335598466102,
+ 55.809934424097946
+ ],
+ [
+ -17.47446647067688,
+ 54.695823674128555
+ ],
+ [
+ -18.420749282499617,
+ 53.54461853638747
+ ],
+ [
+ -19.32114988107176,
+ 52.35776411995351
+ ],
+ [
+ -20.17461372733583,
+ 51.13671529815592
+ ],
+ [
+ -20.980083841171723,
+ 49.88290741582297
+ ],
+ [
+ -21.736505683521976,
+ 48.59779534628392
+ ],
+ [
+ -22.442822274266465,
+ 47.28281931649227
+ ],
+ [
+ -23.097979074347734,
+ 45.93943419977733
+ ],
+ [
+ -23.700921544708308,
+ 44.56908510521784
+ ],
+ [
+ -24.250591484696756,
+ 43.17322202401783
+ ],
+ [
+ -24.74593557578693,
+ 41.75327541888024
+ ],
+ [
+ -25.185895617327397,
+ 40.310709927384934
+ ],
+ [
+ -25.569419511323336,
+ 38.84697065861065
+ ],
+ [
+ -25.89545149818594,
+ 37.36349783951088
+ ],
+ [
+ -26.162932156732477,
+ 35.8617463434149
+ ],
+ [
+ -26.3708093889681,
+ 34.34315151515097
+ ],
+ [
+ -26.51802621477272,
+ 32.80916822804836
+ ],
+ [
+ -26.603526874557538,
+ 31.261246473311093
+ ],
+ [
+ -26.626256829265078,
+ 29.70082647789267
+ ],
+ [
+ -26.626256829265078,
+ 11.865918801477871
+ ],
+ [
+ 0,
+ 0.08407080734751268
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 337,
+ "versionNonce": 1118936332,
+ "isDeleted": false,
+ "id": "OBVgG77jqBVttubO-8vdq",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2202.872578490964,
+ "y": -1967.1038596648962,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 23.59972973431832,
+ "height": 22.73500282506541,
+ "seed": 1737899404,
+ "groupIds": [
+ "flen9nINK9vnXf-Fs5Ue0",
+ "LA_S6hEfLFuoibT7K3arg",
+ "omvCuEw-yZaaMA9sUJfIC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311607367,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -19.51631527964584,
+ 19.61239062275698
+ ],
+ [
+ -19.51631527964584,
+ 19.61239062275698
+ ],
+ [
+ -18.30029748793496,
+ 20.463418288513278
+ ],
+ [
+ -17.048252052823056,
+ 21.26827569562828
+ ],
+ [
+ -15.760176533247499,
+ 22.02584971954132
+ ],
+ [
+ -14.436068488145656,
+ 22.73500282506541
+ ],
+ [
+ 4.083414454672477,
+ 4.119439657010865
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 337,
+ "versionNonce": 814451124,
+ "isDeleted": false,
+ "id": "KWoQ8q1bgV6O9ngOVNten",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2199.605845950801,
+ "y": -1983.5576153240688,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 29.892986928365477,
+ "height": 30.613588617638523,
+ "seed": 1737403404,
+ "groupIds": [
+ "flen9nINK9vnXf-Fs5Ue0",
+ "LA_S6hEfLFuoibT7K3arg",
+ "omvCuEw-yZaaMA9sUJfIC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311607367,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -25.797562445538446,
+ 25.941687665518323
+ ],
+ [
+ -25.797562445538446,
+ 25.941687665518323
+ ],
+ [
+ -24.980507048446146,
+ 27.159768155154122
+ ],
+ [
+ -24.116158503901357,
+ 28.345192108885136
+ ],
+ [
+ -23.206772353777005,
+ 29.49684640215069
+ ],
+ [
+ -22.254601698883377,
+ 30.613588617638523
+ ],
+ [
+ 4.095424482827033,
+ 4.1194420980735
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "line",
+ "version": 337,
+ "versionNonce": 387062668,
+ "isDeleted": false,
+ "id": "REGazF5VKFCTQzMNRFKbP",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2183.2721930142357,
+ "y": -1986.896408033159,
+ "strokeColor": "#000000",
+ "backgroundColor": "#aecbfa",
+ "width": 19.095961853173932,
+ "height": 21.570032535136626,
+ "seed": 1534413452,
+ "groupIds": [
+ "flen9nINK9vnXf-Fs5Ue0",
+ "LA_S6hEfLFuoibT7K3arg",
+ "omvCuEw-yZaaMA9sUJfIC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311607367,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -15.012549839564088,
+ 15.07259753927421
+ ],
+ [
+ -15.012549839564088,
+ 15.07259753927421
+ ],
+ [
+ -14.688467041213968,
+ 16.736367244450225
+ ],
+ [
+ -14.293449403810369,
+ 18.375360163900897
+ ],
+ [
+ -13.828622257227131,
+ 19.987335402129094
+ ],
+ [
+ -13.295114592932032,
+ 21.570032535136626
+ ],
+ [
+ 4.083412013609845,
+ 4.119446980198766
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 337,
+ "versionNonce": 794309428,
+ "isDeleted": false,
+ "id": "2RGRHTUVawHblLxdmnPhC",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2199.3416253314003,
+ "y": -1970.5147125429141,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 11.145317112204834,
+ "height": 11.145317112204834,
+ "seed": 100931852,
+ "groupIds": [
+ "e235bEG92vASATYdRDkSW",
+ "LA_S6hEfLFuoibT7K3arg",
+ "omvCuEw-yZaaMA9sUJfIC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311607367,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 337,
+ "versionNonce": 937347596,
+ "isDeleted": false,
+ "id": "r41NRvAWP0G6p9PkQtixW",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2196.038867588899,
+ "y": -1986.896408033159,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 11.145317112204834,
+ "height": 11.145317112204834,
+ "seed": 1119164300,
+ "groupIds": [
+ "e235bEG92vASATYdRDkSW",
+ "LA_S6hEfLFuoibT7K3arg",
+ "omvCuEw-yZaaMA9sUJfIC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311607367,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "ellipse",
+ "version": 337,
+ "versionNonce": 1372826804,
+ "isDeleted": false,
+ "id": "4iFM40fUx6bWn2AckREZ7",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2179.7052097702085,
+ "y": -1990.2351983011868,
+ "strokeColor": "#000000",
+ "backgroundColor": "#669df6",
+ "width": 11.145317112204834,
+ "height": 11.145317112204834,
+ "seed": 1841448460,
+ "groupIds": [
+ "e235bEG92vASATYdRDkSW",
+ "LA_S6hEfLFuoibT7K3arg",
+ "omvCuEw-yZaaMA9sUJfIC"
+ ],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693311607367,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 1046,
+ "versionNonce": 945544060,
+ "isDeleted": false,
+ "id": "zokrrtrqCTA4sdSchyeKi",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2154.586397934257,
+ "y": -1931.2655151672457,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 79.36479187011719,
+ "height": 16.251850252262567,
+ "seed": 1277535372,
+ "groupIds": [
+ "il6ujkh8lWpwImIyEFFwS",
+ "eL6pb70rQtosyYRIkkTz6",
+ "sUq8jzZlPCueNX8Vkw8R_",
+ "2oxJyo2US2MWfhDq8hBIX",
+ "YAXC5Z8rCERiRzJ07BtDR",
+ "BB12claUSzMP_AqjEGS7C",
+ "omvCuEw-yZaaMA9sUJfIC"
+ ],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1693399360386,
+ "link": null,
+ "locked": false,
+ "fontSize": 14.144467472298855,
+ "fontFamily": 2,
+ "text": "Cloud Armor",
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Cloud Armor",
+ "lineHeight": 1.1489898989898986,
+ "baseline": 13
+ },
+ {
+ "type": "rectangle",
+ "version": 158,
+ "versionNonce": 1795918644,
+ "isDeleted": false,
+ "id": "43t08qUBgpYV35dPr4FqE",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1490.9084466447387,
+ "y": -2228.622425041188,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#4285f4",
+ "width": 1157,
+ "height": 66,
+ "seed": 1070535860,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "aR0cFrtx68oO4Hn-Pp404"
+ }
+ ],
+ "updated": 1693319736943,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 143,
+ "versionNonce": 1168962060,
+ "isDeleted": false,
+ "id": "aR0cFrtx68oO4Hn-Pp404",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1742.4553216447387,
+ "y": -2217.2224250411878,
+ "strokeColor": "#ffffff",
+ "backgroundColor": "#4285f4",
+ "width": 653.90625,
+ "height": 43.199999999999996,
+ "seed": 276268940,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1693319736943,
+ "link": null,
+ "locked": false,
+ "fontSize": 36,
+ "fontFamily": 3,
+ "text": "Serverless PHPIPAM on Cloud Run",
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "43t08qUBgpYV35dPr4FqE",
+ "originalText": "Serverless PHPIPAM on Cloud Run",
+ "lineHeight": 1.2,
+ "baseline": 35
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {
+ "af2541e7127d4fdc679914759de23d8bd87e9264": {
+ "mimeType": "image/png",
+ "id": "af2541e7127d4fdc679914759de23d8bd87e9264",
+ "dataURL": "data:image/png;base64,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",
+ "created": 1693297560288,
+ "lastRetrieved": 1693819633924
+ },
+ "7f10a90d0c745f95f1922694e27ad51a6bf7d09e": {
+ "mimeType": "image/png",
+ "id": "7f10a90d0c745f95f1922694e27ad51a6bf7d09e",
+ "dataURL": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABaAAAAPACAYAAADDhN6oAAAAAXNSR0IArs4c6QAAIABJREFUeF7s3QucXWV9N/pnrb33zOSeTAIk3AwyEEgQRJBwk9urtrbeqh5fq309Kt7xVttq37aeUytg/bRapWptXz1+qlW8oShFvAJSRNF6DdeAIUBICAm5Z257r7XOZ+2ZPUwimEkyz2TP5Ls/n5CQ7P1fz/qu/96z928/61lJcCNAgAABAgQIECBAgAABAgQIECBAgAABAhEEkgg1lSRAgAABAgQIECBAgAABAgQIECBAgAABAkEArQkIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECDWQuP3AAAgAElEQVRAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAgQIAAAQICaD1AgAABAgQIECBAgAABAgQIECBAgAABAlEEBNBRWBUlQIAAAQIECBAgQIAAAQIECBAgQIAAAQG0HiBAgAABAgQIECBAgAABAgQIECBAgACBKAIC6CisihIgQIAAAQIECBAgQIAAAQIECBAgQICAAFoPECBAgAABAgQIECBAgAABAgQIECBAgEAUAQF0FFZFCRAgQIAAAQIECBAgQIAAAQIECBAgQEAArQcIECBAgAABAgQIECBAgAABAgQIECBAIIqAADoKq6IECBAgQIAAAQIECBAgQIAAAQIECBAgIIDWAwQIECBAgAABAgQIECBAgAABAgQIECAQRUAAHYVVUQIECBAgQIAAAQIECBAgQIAAAQIECBAQQOsBAgQIECBAgAABAgQIECBAgAABAgQIEIgiIICOwqooAQIECBAgQIAAAQIECBAgQIAAAQIECAig9QABAgQIECBAgAABAgQIECBAgAABAgQIRBEQQEdhVZQAAQIECBAgQIAAAQIECBAgQIAAAQIEBNB6gAABAgQIECBAgAABAgQIECBAgAABAgSiCAigo7AqSoAAAQIECBAgQIAAAQIECBAg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AAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCCAAAIIIIAAAggg4FoBAmjXloaGIYAAAggggAACCCCAAAIIIIAAAggggAAC3hYggPZ2/Wg9AggggAACCCCAAAIIIIAAAggggAACCCDgWgECaNeWhoYhgAACCCCAAAIIIIAAAggggAACCCCAAALeFiCA9nb9aD0CCCCAAAIIIIAAAggggAACCCCAAAIIIOBaAQJo15aGhiGAAAIIIIAAAggggAACCCCAAAIIIIAAAt4WIID2dv1oPQIIIIAAAggggAACCCDw/9uxYxoAAACEYf5dY2MkdUDKNwIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgQ4e5p8AABSfSURBVKyAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwjQ3/9ZT4AAAQIECBAgQIAAAQIECBAgQIAAgayAAJ29xjACBAgQIECAAAECBAgQIECAAAECBAh8CwwlSpAiDnlErAAAAABJRU5ErkJggg==",
+ "created": 1693297606675,
+ "lastRetrieved": 1693819633924
+ }
+ }
+}
\ No newline at end of file
diff --git a/blueprints/third-party-solutions/phpipam/glb.tf b/blueprints/third-party-solutions/phpipam/glb.tf
new file mode 100644
index 00000000..9016330e
--- /dev/null
+++ b/blueprints/third-party-solutions/phpipam/glb.tf
@@ -0,0 +1,153 @@
+/**
+ * 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 {
+ glb_create = var.phpipam_exposure == "EXTERNAL"
+ iap_sa_email = try(module.project.service_accounts.robots["iap"].email, "")
+}
+
+# Reserved static IP for the Load Balancer
+module "addresses" {
+ source = "../../../modules/net-address"
+ count = local.glb_create ? 1 : 0
+ project_id = var.project_id
+ global_addresses = ["phpipam"]
+}
+
+# Global L7 HTTPS Load Balancer in front of Cloud Run
+module "glb" {
+ source = "../../../modules/net-lb-app-ext"
+ count = local.glb_create ? 1 : 0
+ project_id = module.project.project_id
+ name = "phpipam-glb"
+ address = module.addresses.0.global_addresses["phpipam"].address
+ protocol = "HTTPS"
+
+ backend_service_configs = {
+ default = {
+ backends = [
+ { backend = "phpipam" }
+ ]
+ 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 = {
+ phpipam = {
+ cloudrun = {
+ region = var.region
+ target_service = {
+ name = module.cloud_run.service_name
+ }
+ }
+ }
+ }
+ ssl_certificates = {
+ managed_configs = {
+ default = {
+ domains = [local.domain]
+ }
+ }
+ }
+}
+
+# Cloud Armor configuration
+resource "google_compute_security_policy" "policy" {
+ count = local.glb_create && var.security_policy.enabled ? 1 : 0
+ project = module.project.project_id
+ name = "cloud-run-policy"
+
+ 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.glb_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.glb_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.glb_create && var.iap.enabled ? 1 : 0
+ project = module.project.project_id
+ role = "roles/iap.httpsResourceAccessor"
+ member = "user:${var.iap.email}"
+}
diff --git a/blueprints/third-party-solutions/phpipam/ilb.tf b/blueprints/third-party-solutions/phpipam/ilb.tf
new file mode 100644
index 00000000..814f937f
--- /dev/null
+++ b/blueprints/third-party-solutions/phpipam/ilb.tf
@@ -0,0 +1,89 @@
+/**
+ * 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 {
+ ilb_create = var.phpipam_exposure == "INTERNAL"
+}
+
+# default ssl certificate
+resource "tls_private_key" "default" {
+ algorithm = "RSA"
+ rsa_bits = 2048
+}
+
+resource "tls_self_signed_cert" "default" {
+ private_key_pem = tls_private_key.default.private_key_pem
+ validity_period_hours = 720
+ allowed_uses = [
+ "key_encipherment",
+ "digital_signature",
+ "server_auth",
+ ]
+ subject {
+ common_name = local.domain
+ organization = "ACME Examples, Inc"
+ }
+}
+
+module "ilb-l7" {
+ source = "../../../modules/net-lb-app-int"
+ count = local.ilb_create ? 1 : 0
+ project_id = var.project_id
+ name = "ilb-l7-cr"
+ protocol = "HTTPS"
+ region = var.region
+
+ backend_service_configs = {
+ default = {
+ project_id = var.project_id
+ backends = [
+ {
+ group = "phpipam"
+ }
+ ]
+ health_checks = []
+ }
+ }
+ health_check_configs = {
+ default = {
+ https = { port = 443 }
+ }
+ }
+ neg_configs = {
+ phpipam = {
+ project_id = var.project_id
+ cloudrun = {
+ region = var.region
+ target_service = {
+ name = module.cloud_run.service_name
+ }
+ }
+ }
+ }
+ ssl_certificates = {
+ create_configs = {
+ default = {
+ # certificate and key could also be read via file() from external files
+ certificate = tls_self_signed_cert.default.cert_pem
+ private_key = tls_private_key.default.private_key_pem
+ }
+ }
+ }
+ vpc_config = {
+ network = local.network
+ subnetwork = local.subnetwork
+ }
+}
diff --git a/blueprints/third-party-solutions/phpipam/images/phpipam.png b/blueprints/third-party-solutions/phpipam/images/phpipam.png
new file mode 100644
index 00000000..6d032778
Binary files /dev/null and b/blueprints/third-party-solutions/phpipam/images/phpipam.png differ
diff --git a/blueprints/third-party-solutions/phpipam/images/phpipam_admin.png b/blueprints/third-party-solutions/phpipam/images/phpipam_admin.png
new file mode 100644
index 00000000..aea68b03
Binary files /dev/null and b/blueprints/third-party-solutions/phpipam/images/phpipam_admin.png differ
diff --git a/blueprints/third-party-solutions/phpipam/images/phpipam_db.png b/blueprints/third-party-solutions/phpipam/images/phpipam_db.png
new file mode 100644
index 00000000..9d218a42
Binary files /dev/null and b/blueprints/third-party-solutions/phpipam/images/phpipam_db.png differ
diff --git a/blueprints/third-party-solutions/phpipam/images/phpipam_home.png b/blueprints/third-party-solutions/phpipam/images/phpipam_home.png
new file mode 100644
index 00000000..49168616
Binary files /dev/null and b/blueprints/third-party-solutions/phpipam/images/phpipam_home.png differ
diff --git a/blueprints/third-party-solutions/phpipam/images/phpipam_install.png b/blueprints/third-party-solutions/phpipam/images/phpipam_install.png
new file mode 100644
index 00000000..35835cc9
Binary files /dev/null and b/blueprints/third-party-solutions/phpipam/images/phpipam_install.png differ
diff --git a/blueprints/third-party-solutions/phpipam/main.tf b/blueprints/third-party-solutions/phpipam/main.tf
new file mode 100644
index 00000000..7998dfa2
--- /dev/null
+++ b/blueprints/third-party-solutions/phpipam/main.tf
@@ -0,0 +1,144 @@
+/**
+ * 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 {
+ cloudsql_conf = {
+ database_version = "MYSQL_8_0"
+ tier = "db-g1-small"
+ db = "phpipam"
+ user = "admin"
+ }
+ connector = var.connector == null ? module.cloud_run.vpc_connector : var.connector
+ domain = (
+ var.custom_domain != null ? var.custom_domain : (
+ var.phpipam_exposure == "EXTERNAL" ?
+ "${module.addresses.0.global_addresses["phpipam"].address}.nip.io" : "phpipam.internal")
+ )
+ iam = {
+ # CloudSQL
+ "roles/cloudsql.admin" = var.admin_principals
+ "roles/cloudsql.client" = var.admin_principals
+ "roles/cloudsql.instanceUser" = var.admin_principals
+ # common roles
+ "roles/logging.admin" = var.admin_principals
+ "roles/iam.serviceAccountUser" = var.admin_principals
+ "roles/iam.serviceAccountTokenCreator" = var.admin_principals
+ }
+ network = var.vpc_config == null ? module.vpc.0.self_link : var.vpc_config.network
+ phpipam_password = var.phpipam_password == null ? random_password.phpipam_password.result : var.phpipam_password
+ subnetwork = var.vpc_config == null ? module.vpc.0.subnet_self_links["${var.region}/ilb"] : var.vpc_config.subnetwork
+}
+
+
+# either create a project or set up the given one
+module "project" {
+ source = "../../../modules/project"
+ billing_account = try(var.project_create.billing_account_id, null)
+ iam = var.project_create != null ? local.iam : {}
+ name = var.project_id
+ parent = try(var.project_create.parent, null)
+ prefix = var.project_create == null ? null : var.prefix
+ project_create = var.project_create != null
+ services = [
+ "iap.googleapis.com",
+ "logging.googleapis.com",
+ "monitoring.googleapis.com",
+ "run.googleapis.com",
+ "servicenetworking.googleapis.com",
+ "sqladmin.googleapis.com",
+ "sql-component.googleapis.com",
+ "vpcaccess.googleapis.com"
+ ]
+}
+
+
+# create a VPC for CloudSQL and ILB
+module "vpc" {
+ source = "../../../modules/net-vpc"
+ count = var.vpc_config == null ? 1 : 0
+ project_id = module.project.project_id
+ name = "${var.prefix}-sql-vpc"
+
+ psa_config = {
+ ranges = {
+ cloud-sql = var.ip_ranges.psa
+ }
+ }
+ subnets = [
+ {
+ ip_cidr_range = var.ip_ranges.ilb
+ name = "ilb"
+ region = var.region
+ }
+ ]
+}
+
+resource "random_password" "phpipam_password" {
+ length = 8
+}
+
+# create the Cloud Run service
+module "cloud_run" {
+ source = "../../../modules/cloud-run"
+ project_id = module.project.project_id
+ name = "${var.prefix}-cr-phpipam"
+ prefix = var.prefix
+ ingress_settings = "all"
+ region = var.region
+
+ containers = {
+ phpipam = {
+ image = var.phpipam_config.image
+ ports = {
+ http = {
+ name = "http1"
+ protocol = null
+ container_port = var.phpipam_config.port
+ }
+ }
+ env_from = null
+ # set up the database connection
+ env = {
+ "TZ" = "Europe/Rome"
+ "IPAM_DATABASE_HOST" = module.cloudsql.ip
+ "IPAM_DATABASE_USER" = local.cloudsql_conf.user
+ "IPAM_DATABASE_PASS" = var.cloudsql_password == null ? module.cloudsql.user_passwords[local.cloudsql_conf.user] : var.cloudsql_password
+ "IPAM_DATABASE_NAME" = local.cloudsql_conf.db
+ "IPAM_DATABASE_PORT" = "3306"
+ }
+ }
+ }
+ iam = local.glb_create && var.iap.enabled ? {
+ "roles/run.invoker" : ["serviceAccount:${local.iap_sa_email}"]
+ } : {
+ "roles/run.invoker" : [var.cloud_run_invoker]
+ }
+ revision_annotations = {
+ autoscaling = {
+ min_scale = 1
+ max_scale = 2
+ }
+ # connect to CloudSQL
+ cloudsql_instances = [module.cloudsql.connection_name]
+ # allow all traffic
+ vpcaccess_egress = "private-ranges-only"
+ vpcaccess_connector = local.connector
+ }
+ vpc_connector_create = var.create_connector ? {
+ ip_cidr_range = var.ip_ranges.connector
+ vpc_self_link = local.network
+ } : null
+}
diff --git a/blueprints/third-party-solutions/phpipam/outputs.tf b/blueprints/third-party-solutions/phpipam/outputs.tf
new file mode 100644
index 00000000..0795c0f2
--- /dev/null
+++ b/blueprints/third-party-solutions/phpipam/outputs.tf
@@ -0,0 +1,48 @@
+/**
+ * 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.
+ */
+
+output "cloud_run_service" {
+ description = "CloudRun service URL."
+ value = module.cloud_run.service.status[0].url
+ sensitive = true
+}
+
+output "cloudsql_password" {
+ description = "CloudSQL password."
+ value = var.cloudsql_password == null ? module.cloudsql.user_passwords[local.cloudsql_conf.user] : var.cloudsql_password
+ sensitive = true
+}
+
+output "phpipam_ip_address" {
+ description = "PHPIPAM IP Address either external or internal according to app exposure."
+ value = local.glb_create ? module.addresses.0.global_addresses["phpipam"].address : module.ilb-l7.0.address
+}
+
+output "phpipam_password" {
+ description = "PHPIPAM user password."
+ value = local.phpipam_password
+ sensitive = true
+}
+
+output "phpipam_url" {
+ description = "PHPIPAM website url."
+ value = local.domain
+}
+
+output "phpipam_user" {
+ description = "PHPIPAM username."
+ value = "admin"
+}
diff --git a/blueprints/third-party-solutions/phpipam/terraform.tfvars.sample b/blueprints/third-party-solutions/phpipam/terraform.tfvars.sample
new file mode 100644
index 00000000..776bedf9
--- /dev/null
+++ b/blueprints/third-party-solutions/phpipam/terraform.tfvars.sample
@@ -0,0 +1,2 @@
+prefix = "phpipam"
+project_id = "my-phpipam-project"
diff --git a/blueprints/third-party-solutions/phpipam/variables.tf b/blueprints/third-party-solutions/phpipam/variables.tf
new file mode 100644
index 00000000..75d3d2c6
--- /dev/null
+++ b/blueprints/third-party-solutions/phpipam/variables.tf
@@ -0,0 +1,156 @@
+/**
+ * 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.
+ */
+
+# Documentation: https://cloud.google.com/run/docs/securing/managing-access#making_a_service_public
+
+variable "admin_principals" {
+ description = "Users, groups and/or service accounts that are assigned roles, in IAM format (`group:foo@example.com`)."
+ type = list(string)
+ default = []
+}
+
+variable "cloud_run_invoker" {
+ description = "IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone)."
+ type = string
+ default = "allUsers"
+}
+
+variable "cloudsql_password" {
+ description = "CloudSQL password (will be randomly generated by default)."
+ type = string
+ default = null
+}
+
+variable "connector" {
+ description = "Existing VPC serverless connector to use if not creating a new one."
+ type = string
+ default = null
+}
+
+variable "create_connector" {
+ description = "Should a VPC serverless connector be created or not."
+ type = bool
+ default = true
+}
+
+variable "custom_domain" {
+ description = "Cloud Run service custom domain for GLB."
+ 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 = {}
+}
+
+# PSA: documentation: https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range
+variable "ip_ranges" {
+ description = "CIDR blocks: VPC serverless connector, Private Service Access(PSA) for CloudSQL, CloudSQL VPC."
+ type = object({
+ connector = string
+ psa = string
+ ilb = string
+ })
+ default = {
+ connector = "10.8.0.0/28"
+ psa = "10.60.0.0/24"
+ ilb = "10.128.0.0/28"
+ }
+}
+
+variable "phpipam_config" {
+ description = "PHPIpam configuration."
+ type = object({
+ image = optional(string, "phpipam/phpipam-www:latest")
+ port = optional(number, 80)
+ })
+ default = {
+ image = "phpipam/phpipam-www:latest"
+ port = 80
+ }
+}
+
+variable "phpipam_exposure" {
+ description = "Whether to expose the application publicly via GLB or internally via ILB, default GLB."
+ type = string
+ default = "EXTERNAL"
+ validation {
+ condition = var.phpipam_exposure == "INTERNAL" || var.phpipam_exposure == "EXTERNAL"
+ error_message = "phpipam_exposure supports only 'INTERNAL' or 'EXTERNAL'"
+ }
+}
+
+variable "phpipam_password" {
+ description = "Password for the phpipam user (will be randomly generated by default)."
+ type = string
+ default = null
+}
+
+variable "prefix" {
+ description = "Prefix used for resource names."
+ type = string
+ nullable = false
+ validation {
+ condition = var.prefix != ""
+ error_message = "Prefix cannot be empty."
+ }
+}
+
+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 "region" {
+ description = "Region for the created resources."
+ type = string
+ default = "europe-west4"
+}
+
+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 = {}
+}
+
+variable "vpc_config" {
+ description = "VPC Network and subnetwork self links for internal LB setup."
+ type = object({
+ network = string
+ subnetwork = string
+ })
+ default = null
+}
diff --git a/default-versions.tf b/default-versions.tf
index f494b243..91a91a31 100644
--- a/default-versions.tf
+++ b/default-versions.tf
@@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/fast/stages/0-bootstrap/organization.tf b/fast/stages/0-bootstrap/organization.tf
index 946e3d7b..d9f62221 100644
--- a/fast/stages/0-bootstrap/organization.tf
+++ b/fast/stages/0-bootstrap/organization.tf
@@ -88,8 +88,9 @@ module "organization" {
)
# delegated role grant for resource manager service account
iam_bindings = {
- (module.organization.custom_role_id[var.custom_role_names.organization_iam_admin]) = {
+ organization_iam_admin_conditional = {
members = [module.automation-tf-resman-sa.iam_email]
+ role = module.organization.custom_role_id[var.custom_role_names.organization_iam_admin]
condition = {
expression = format(
"api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])",
diff --git a/fast/stages/1-resman/outputs.tf b/fast/stages/1-resman/outputs.tf
index 41994e98..fcfa4ff3 100644
--- a/fast/stages/1-resman/outputs.tf
+++ b/fast/stages/1-resman/outputs.tf
@@ -223,9 +223,9 @@ locals {
tfvars = {
folder_ids = local.folder_ids
service_accounts = local.service_accounts
- tag_keys = { for k, v in module.organization.tag_keys : k => v.id }
+ tag_keys = { for k, v in try(module.organization.tag_keys, {}) : k => v.id }
tag_names = var.tag_names
- tag_values = { for k, v in module.organization.tag_values : k => v.id }
+ tag_values = { for k, v in try(module.organization.tag_values, {}) : k => v.id }
}
}
diff --git a/fast/stages/2-networking-a-peering/README.md b/fast/stages/2-networking-a-peering/README.md
index 75c5fb66..f536b943 100644
--- a/fast/stages/2-networking-a-peering/README.md
+++ b/fast/stages/2-networking-a-peering/README.md
@@ -406,10 +406,10 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [factories_config](variables.tf#L80) | Configuration for network resource factories. | object({…})
| | {…}
| |
| [outputs_location](variables.tf#L121) | 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. | object({…})
| | {}
| |
-| [psa_ranges](variables.tf#L138) | IP ranges used for Private Service Access (CloudSQL, etc.). | object({…})
| | null
| |
-| [regions](variables.tf#L159) | Region definitions. | object({…})
| | {…}
| |
-| [service_accounts](variables.tf#L171) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
-| [vpn_onprem_primary_config](variables.tf#L185) | VPN gateway configuration for onprem interconnection in the primary region. | object({…})
| | null
| |
+| [psa_ranges](variables.tf#L138) | IP ranges used for Private Service Access (CloudSQL, etc.). | object({…})
| | null
| |
+| [regions](variables.tf#L155) | Region definitions. | object({…})
| | {…}
| |
+| [service_accounts](variables.tf#L167) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
+| [vpn_onprem_primary_config](variables.tf#L181) | VPN gateway configuration for onprem interconnection in the primary region. | object({…})
| | null
| |
## Outputs
diff --git a/fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml
index ad5a06d5..444903eb 100644
--- a/fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml
+++ b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml
@@ -4,5 +4,5 @@ region: europe-west1
description: Default subnet for dev Data Platform
ip_cidr_range: 10.127.48.0/24
secondary_ip_ranges:
- pods: 100.64.0.0/24
+ pods: 100.64.0.0/16
services: 100.64.1.0/24
diff --git a/fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml
index 9844d0f0..74ca5f42 100644
--- a/fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml
+++ b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml
@@ -4,5 +4,5 @@ region: europe-west1
description: Default subnet for prod gke nodes
ip_cidr_range: 10.127.49.0/24
secondary_ip_ranges:
- pods: 100.65.0.0/24
+ pods: 100.65.0.0/16
services: 100.65.1.0/24
diff --git a/fast/stages/2-networking-a-peering/landing.tf b/fast/stages/2-networking-a-peering/landing.tf
index 013c6e86..e2309f1b 100644
--- a/fast/stages/2-networking-a-peering/landing.tf
+++ b/fast/stages/2-networking-a-peering/landing.tf
@@ -55,7 +55,9 @@ module "landing-vpc" {
private = true
restricted = true
}
- data_folder = "${var.factories_config.data_dir}/subnets/landing"
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/landing"
+ }
}
module "landing-firewall" {
diff --git a/fast/stages/2-networking-a-peering/spoke-dev.tf b/fast/stages/2-networking-a-peering/spoke-dev.tf
index 838ba6a4..bfff002b 100644
--- a/fast/stages/2-networking-a-peering/spoke-dev.tf
+++ b/fast/stages/2-networking-a-peering/spoke-dev.tf
@@ -46,12 +46,14 @@ module "dev-spoke-project" {
}
module "dev-spoke-vpc" {
- source = "../../../modules/net-vpc"
- project_id = module.dev-spoke-project.project_id
- name = "dev-spoke-0"
- mtu = 1500
- data_folder = "${var.factories_config.data_dir}/subnets/dev"
- psa_config = try(var.psa_ranges.dev, null)
+ source = "../../../modules/net-vpc"
+ project_id = module.dev-spoke-project.project_id
+ name = "dev-spoke-0"
+ mtu = 1500
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/dev"
+ }
+ psa_config = try(var.psa_ranges.dev, null)
# set explicit routes for googleapis in case the default route is deleted
create_googleapis_routes = {
private = true
diff --git a/fast/stages/2-networking-a-peering/spoke-prod.tf b/fast/stages/2-networking-a-peering/spoke-prod.tf
index 7569647e..505005bd 100644
--- a/fast/stages/2-networking-a-peering/spoke-prod.tf
+++ b/fast/stages/2-networking-a-peering/spoke-prod.tf
@@ -45,12 +45,14 @@ module "prod-spoke-project" {
}
module "prod-spoke-vpc" {
- source = "../../../modules/net-vpc"
- project_id = module.prod-spoke-project.project_id
- name = "prod-spoke-0"
- mtu = 1500
- data_folder = "${var.factories_config.data_dir}/subnets/prod"
- psa_config = try(var.psa_ranges.prod, null)
+ source = "../../../modules/net-vpc"
+ project_id = module.prod-spoke-project.project_id
+ name = "prod-spoke-0"
+ mtu = 1500
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/prod"
+ }
+ psa_config = try(var.psa_ranges.prod, null)
# set explicit routes for googleapis in case the default route is deleted
create_googleapis_routes = {
private = true
diff --git a/fast/stages/2-networking-a-peering/variables.tf b/fast/stages/2-networking-a-peering/variables.tf
index a0ff0a79..d0190dfa 100644
--- a/fast/stages/2-networking-a-peering/variables.tf
+++ b/fast/stages/2-networking-a-peering/variables.tf
@@ -139,18 +139,14 @@ variable "psa_ranges" {
description = "IP ranges used for Private Service Access (CloudSQL, etc.)."
type = object({
dev = object({
- ranges = map(string)
- routes = object({
- export = bool
- import = bool
- })
+ ranges = map(string)
+ export_routes = optional(bool, false)
+ import_routes = optional(bool, false)
})
prod = object({
- ranges = map(string)
- routes = object({
- export = bool
- import = bool
- })
+ ranges = map(string)
+ export_routes = optional(bool, false)
+ import_routes = optional(bool, false)
})
})
default = null
diff --git a/fast/stages/2-networking-b-vpn/README.md b/fast/stages/2-networking-b-vpn/README.md
index e87cee14..3cbf75f3 100644
--- a/fast/stages/2-networking-b-vpn/README.md
+++ b/fast/stages/2-networking-b-vpn/README.md
@@ -430,11 +430,11 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [dns](variables.tf#L72) | Onprem DNS resolvers. | map(list(string))
| | {…}
| |
| [factories_config](variables.tf#L80) | Configuration for network resource factories. | object({…})
| | {…}
| |
| [outputs_location](variables.tf#L121) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
-| [psa_ranges](variables.tf#L138) | IP ranges used for Private Service Access (CloudSQL, etc.). | object({…})
| | null
| |
-| [regions](variables.tf#L159) | Region definitions. | object({…})
| | {…}
| |
-| [service_accounts](variables.tf#L171) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
+| [psa_ranges](variables.tf#L138) | IP ranges used for Private Service Access (CloudSQL, etc.). | object({…})
| | null
| |
+| [regions](variables.tf#L155) | Region definitions. | object({…})
| | {…}
| |
+| [service_accounts](variables.tf#L167) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
| [vpn_configs](variables-vpn.tf#L17) | Hub to spokes VPN configurations. | object({…})
| | {…}
| |
-| [vpn_onprem_primary_config](variables.tf#L185) | VPN gateway configuration for onprem interconnection in the primary region. | object({…})
| | null
| |
+| [vpn_onprem_primary_config](variables.tf#L181) | VPN gateway configuration for onprem interconnection in the primary region. | object({…})
| | null
| |
## Outputs
diff --git a/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml
index ad5a06d5..444903eb 100644
--- a/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml
+++ b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml
@@ -4,5 +4,5 @@ region: europe-west1
description: Default subnet for dev Data Platform
ip_cidr_range: 10.127.48.0/24
secondary_ip_ranges:
- pods: 100.64.0.0/24
+ pods: 100.64.0.0/16
services: 100.64.1.0/24
diff --git a/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml
index 9844d0f0..74ca5f42 100644
--- a/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml
+++ b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml
@@ -4,5 +4,5 @@ region: europe-west1
description: Default subnet for prod gke nodes
ip_cidr_range: 10.127.49.0/24
secondary_ip_ranges:
- pods: 100.65.0.0/24
+ pods: 100.65.0.0/16
services: 100.65.1.0/24
diff --git a/fast/stages/2-networking-b-vpn/landing.tf b/fast/stages/2-networking-b-vpn/landing.tf
index 013c6e86..e2309f1b 100644
--- a/fast/stages/2-networking-b-vpn/landing.tf
+++ b/fast/stages/2-networking-b-vpn/landing.tf
@@ -55,7 +55,9 @@ module "landing-vpc" {
private = true
restricted = true
}
- data_folder = "${var.factories_config.data_dir}/subnets/landing"
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/landing"
+ }
}
module "landing-firewall" {
diff --git a/fast/stages/2-networking-b-vpn/spoke-dev.tf b/fast/stages/2-networking-b-vpn/spoke-dev.tf
index 838ba6a4..bfff002b 100644
--- a/fast/stages/2-networking-b-vpn/spoke-dev.tf
+++ b/fast/stages/2-networking-b-vpn/spoke-dev.tf
@@ -46,12 +46,14 @@ module "dev-spoke-project" {
}
module "dev-spoke-vpc" {
- source = "../../../modules/net-vpc"
- project_id = module.dev-spoke-project.project_id
- name = "dev-spoke-0"
- mtu = 1500
- data_folder = "${var.factories_config.data_dir}/subnets/dev"
- psa_config = try(var.psa_ranges.dev, null)
+ source = "../../../modules/net-vpc"
+ project_id = module.dev-spoke-project.project_id
+ name = "dev-spoke-0"
+ mtu = 1500
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/dev"
+ }
+ psa_config = try(var.psa_ranges.dev, null)
# set explicit routes for googleapis in case the default route is deleted
create_googleapis_routes = {
private = true
diff --git a/fast/stages/2-networking-b-vpn/spoke-prod.tf b/fast/stages/2-networking-b-vpn/spoke-prod.tf
index 7569647e..505005bd 100644
--- a/fast/stages/2-networking-b-vpn/spoke-prod.tf
+++ b/fast/stages/2-networking-b-vpn/spoke-prod.tf
@@ -45,12 +45,14 @@ module "prod-spoke-project" {
}
module "prod-spoke-vpc" {
- source = "../../../modules/net-vpc"
- project_id = module.prod-spoke-project.project_id
- name = "prod-spoke-0"
- mtu = 1500
- data_folder = "${var.factories_config.data_dir}/subnets/prod"
- psa_config = try(var.psa_ranges.prod, null)
+ source = "../../../modules/net-vpc"
+ project_id = module.prod-spoke-project.project_id
+ name = "prod-spoke-0"
+ mtu = 1500
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/prod"
+ }
+ psa_config = try(var.psa_ranges.prod, null)
# set explicit routes for googleapis in case the default route is deleted
create_googleapis_routes = {
private = true
diff --git a/fast/stages/2-networking-b-vpn/variables.tf b/fast/stages/2-networking-b-vpn/variables.tf
index a0ff0a79..d0190dfa 100644
--- a/fast/stages/2-networking-b-vpn/variables.tf
+++ b/fast/stages/2-networking-b-vpn/variables.tf
@@ -139,18 +139,14 @@ variable "psa_ranges" {
description = "IP ranges used for Private Service Access (CloudSQL, etc.)."
type = object({
dev = object({
- ranges = map(string)
- routes = object({
- export = bool
- import = bool
- })
+ ranges = map(string)
+ export_routes = optional(bool, false)
+ import_routes = optional(bool, false)
})
prod = object({
- ranges = map(string)
- routes = object({
- export = bool
- import = bool
- })
+ ranges = map(string)
+ export_routes = optional(bool, false)
+ import_routes = optional(bool, false)
})
})
default = null
diff --git a/fast/stages/2-networking-c-nva/README.md b/fast/stages/2-networking-c-nva/README.md
index dfc41a0c..5d7cc9b4 100644
--- a/fast/stages/2-networking-c-nva/README.md
+++ b/fast/stages/2-networking-c-nva/README.md
@@ -488,11 +488,11 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [gcp_ranges](variables.tf#L111) | GCP address ranges in name => range format. | map(string)
| | {…}
| |
| [onprem_cidr](variables.tf#L126) | Onprem addresses in name => range format. | map(string)
| | {…}
| |
| [outputs_location](variables.tf#L144) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
-| [psa_ranges](variables.tf#L161) | IP ranges used for Private Service Access (e.g. CloudSQL). Ranges is in name => range format. | object({…})
| | null
| |
-| [regions](variables.tf#L182) | Region definitions. | object({…})
| | {…}
| |
-| [service_accounts](variables.tf#L194) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
-| [vpn_onprem_primary_config](variables.tf#L208) | VPN gateway configuration for onprem interconnection in the primary region. | object({…})
| | null
| |
-| [vpn_onprem_secondary_config](variables.tf#L251) | VPN gateway configuration for onprem interconnection in the secondary region. | object({…})
| | null
| |
+| [psa_ranges](variables.tf#L161) | IP ranges used for Private Service Access (e.g. CloudSQL). Ranges is in name => range format. | object({…})
| | null
| |
+| [regions](variables.tf#L178) | Region definitions. | object({…})
| | {…}
| |
+| [service_accounts](variables.tf#L190) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
+| [vpn_onprem_primary_config](variables.tf#L204) | VPN gateway configuration for onprem interconnection in the primary region. | object({…})
| | null
| |
+| [vpn_onprem_secondary_config](variables.tf#L247) | VPN gateway configuration for onprem interconnection in the secondary region. | object({…})
| | null
| |
## Outputs
diff --git a/fast/stages/2-networking-c-nva/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/dev/dev-dataplatform-ew1.yaml
index ad5a06d5..444903eb 100644
--- a/fast/stages/2-networking-c-nva/data/subnets/dev/dev-dataplatform-ew1.yaml
+++ b/fast/stages/2-networking-c-nva/data/subnets/dev/dev-dataplatform-ew1.yaml
@@ -4,5 +4,5 @@ region: europe-west1
description: Default subnet for dev Data Platform
ip_cidr_range: 10.127.48.0/24
secondary_ip_ranges:
- pods: 100.64.0.0/24
+ pods: 100.64.0.0/16
services: 100.64.1.0/24
diff --git a/fast/stages/2-networking-c-nva/landing.tf b/fast/stages/2-networking-c-nva/landing.tf
index e7329a43..fb19c31b 100644
--- a/fast/stages/2-networking-c-nva/landing.tf
+++ b/fast/stages/2-networking-c-nva/landing.tf
@@ -54,7 +54,9 @@ module "landing-untrusted-vpc" {
logging = false
}
create_googleapis_routes = null
- data_folder = "${var.factories_config.data_dir}/subnets/landing-untrusted"
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/landing-untrusted"
+ }
}
module "landing-untrusted-firewall" {
@@ -110,7 +112,9 @@ 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"
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/landing-trusted"
+ }
dns_policy = {
inbound = true
}
diff --git a/fast/stages/2-networking-c-nva/spoke-dev.tf b/fast/stages/2-networking-c-nva/spoke-dev.tf
index a90d25aa..0f6e8b8f 100644
--- a/fast/stages/2-networking-c-nva/spoke-dev.tf
+++ b/fast/stages/2-networking-c-nva/spoke-dev.tf
@@ -45,11 +45,13 @@ module "dev-spoke-project" {
}
module "dev-spoke-vpc" {
- source = "../../../modules/net-vpc"
- project_id = module.dev-spoke-project.project_id
- name = "dev-spoke-0"
- mtu = 1500
- data_folder = "${var.factories_config.data_dir}/subnets/dev"
+ source = "../../../modules/net-vpc"
+ project_id = module.dev-spoke-project.project_id
+ name = "dev-spoke-0"
+ mtu = 1500
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/dev"
+ }
delete_default_routes_on_create = true
psa_config = try(var.psa_ranges.dev, null)
# Set explicit routes for googleapis; send everything else to NVAs
diff --git a/fast/stages/2-networking-c-nva/spoke-prod.tf b/fast/stages/2-networking-c-nva/spoke-prod.tf
index 8dd5af44..98959509 100644
--- a/fast/stages/2-networking-c-nva/spoke-prod.tf
+++ b/fast/stages/2-networking-c-nva/spoke-prod.tf
@@ -44,11 +44,13 @@ module "prod-spoke-project" {
}
module "prod-spoke-vpc" {
- source = "../../../modules/net-vpc"
- project_id = module.prod-spoke-project.project_id
- name = "prod-spoke-0"
- mtu = 1500
- data_folder = "${var.factories_config.data_dir}/subnets/prod"
+ source = "../../../modules/net-vpc"
+ project_id = module.prod-spoke-project.project_id
+ name = "prod-spoke-0"
+ mtu = 1500
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/prod"
+ }
delete_default_routes_on_create = true
psa_config = try(var.psa_ranges.prod, null)
# Set explicit routes for googleapis; send everything else to NVAs
diff --git a/fast/stages/2-networking-c-nva/variables.tf b/fast/stages/2-networking-c-nva/variables.tf
index 1b4ad4ec..67697a22 100644
--- a/fast/stages/2-networking-c-nva/variables.tf
+++ b/fast/stages/2-networking-c-nva/variables.tf
@@ -162,18 +162,14 @@ variable "psa_ranges" {
description = "IP ranges used for Private Service Access (e.g. CloudSQL). Ranges is in name => range format."
type = object({
dev = object({
- ranges = map(string)
- routes = object({
- export = bool
- import = bool
- })
+ ranges = map(string)
+ export_routes = optional(bool, false)
+ import_routes = optional(bool, false)
})
prod = object({
- ranges = map(string)
- routes = object({
- export = bool
- import = bool
- })
+ ranges = map(string)
+ export_routes = optional(bool, false)
+ import_routes = optional(bool, false)
})
})
default = null
diff --git a/fast/stages/2-networking-d-separate-envs/README.md b/fast/stages/2-networking-d-separate-envs/README.md
index 7514454f..31a69ef6 100644
--- a/fast/stages/2-networking-d-separate-envs/README.md
+++ b/fast/stages/2-networking-d-separate-envs/README.md
@@ -348,11 +348,11 @@ Regions are defined via the `regions` variable which sets up a mapping between t
| [dns](variables.tf#L72) | Onprem DNS resolvers. | map(list(string))
| | {…}
| |
| [factories_config](variables.tf#L81) | Configuration for network resource factories. | object({…})
| | {…}
| |
| [outputs_location](variables.tf#L122) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
-| [psa_ranges](variables.tf#L139) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…})
| | null
| |
-| [regions](variables.tf#L160) | Region definitions. | object({…})
| | {…}
| |
-| [service_accounts](variables.tf#L170) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
-| [vpn_onprem_dev_primary_config](variables.tf#L184) | VPN gateway configuration for onprem interconnection from dev in the primary region. | object({…})
| | null
| |
-| [vpn_onprem_prod_primary_config](variables.tf#L227) | VPN gateway configuration for onprem interconnection from prod in the primary region. | object({…})
| | null
| |
+| [psa_ranges](variables.tf#L139) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…})
| | null
| |
+| [regions](variables.tf#L156) | Region definitions. | object({…})
| | {…}
| |
+| [service_accounts](variables.tf#L166) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
+| [vpn_onprem_dev_primary_config](variables.tf#L180) | VPN gateway configuration for onprem interconnection from dev in the primary region. | object({…})
| | null
| |
+| [vpn_onprem_prod_primary_config](variables.tf#L223) | VPN gateway configuration for onprem interconnection from prod in the primary region. | object({…})
| | null
| |
## Outputs
diff --git a/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml
index ad5a06d5..444903eb 100644
--- a/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml
+++ b/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml
@@ -4,5 +4,5 @@ region: europe-west1
description: Default subnet for dev Data Platform
ip_cidr_range: 10.127.48.0/24
secondary_ip_ranges:
- pods: 100.64.0.0/24
+ pods: 100.64.0.0/16
services: 100.64.1.0/24
diff --git a/fast/stages/2-networking-d-separate-envs/spoke-dev.tf b/fast/stages/2-networking-d-separate-envs/spoke-dev.tf
index b5b485be..61562f44 100644
--- a/fast/stages/2-networking-d-separate-envs/spoke-dev.tf
+++ b/fast/stages/2-networking-d-separate-envs/spoke-dev.tf
@@ -46,12 +46,14 @@ module "dev-spoke-project" {
}
module "dev-spoke-vpc" {
- source = "../../../modules/net-vpc"
- project_id = module.dev-spoke-project.project_id
- name = "dev-spoke-0"
- mtu = 1500
- data_folder = "${var.factories_config.data_dir}/subnets/dev"
- psa_config = try(var.psa_ranges.dev, null)
+ source = "../../../modules/net-vpc"
+ project_id = module.dev-spoke-project.project_id
+ name = "dev-spoke-0"
+ mtu = 1500
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/dev"
+ }
+ psa_config = try(var.psa_ranges.dev, null)
# set explicit routes for googleapis in case the default route is deleted
create_googleapis_routes = {
private = true
diff --git a/fast/stages/2-networking-d-separate-envs/spoke-prod.tf b/fast/stages/2-networking-d-separate-envs/spoke-prod.tf
index bf43728d..7b42f546 100644
--- a/fast/stages/2-networking-d-separate-envs/spoke-prod.tf
+++ b/fast/stages/2-networking-d-separate-envs/spoke-prod.tf
@@ -45,12 +45,14 @@ module "prod-spoke-project" {
}
module "prod-spoke-vpc" {
- source = "../../../modules/net-vpc"
- project_id = module.prod-spoke-project.project_id
- name = "prod-spoke-0"
- mtu = 1500
- data_folder = "${var.factories_config.data_dir}/subnets/prod"
- psa_config = try(var.psa_ranges.prod, null)
+ source = "../../../modules/net-vpc"
+ project_id = module.prod-spoke-project.project_id
+ name = "prod-spoke-0"
+ mtu = 1500
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/prod"
+ }
+ psa_config = try(var.psa_ranges.prod, null)
# set explicit routes for googleapis in case the default route is deleted
create_googleapis_routes = {
private = true
diff --git a/fast/stages/2-networking-d-separate-envs/variables.tf b/fast/stages/2-networking-d-separate-envs/variables.tf
index 29d4788a..8edcd72c 100644
--- a/fast/stages/2-networking-d-separate-envs/variables.tf
+++ b/fast/stages/2-networking-d-separate-envs/variables.tf
@@ -140,18 +140,14 @@ variable "psa_ranges" {
description = "IP ranges used for Private Service Access (e.g. CloudSQL)."
type = object({
dev = object({
- ranges = map(string)
- routes = object({
- export = bool
- import = bool
- })
+ ranges = map(string)
+ export_routes = optional(bool, false)
+ import_routes = optional(bool, false)
})
prod = object({
- ranges = map(string)
- routes = object({
- export = bool
- import = bool
- })
+ ranges = map(string)
+ export_routes = optional(bool, false)
+ import_routes = optional(bool, false)
})
})
default = null
diff --git a/fast/stages/2-networking-e-nva-bgp/README.md b/fast/stages/2-networking-e-nva-bgp/README.md
index eabd74db..be1526c8 100644
--- a/fast/stages/2-networking-e-nva-bgp/README.md
+++ b/fast/stages/2-networking-e-nva-bgp/README.md
@@ -515,12 +515,12 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [ncc_asn](variables.tf#L126) | The NCC Cloud Routers ASN configuration. | map(number)
| | {…}
| |
| [onprem_cidr](variables.tf#L137) | Onprem addresses in name => range format. | map(string)
| | {…}
| |
| [outputs_location](variables.tf#L155) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
-| [psa_ranges](variables.tf#L172) | IP ranges used for Private Service Access (e.g. CloudSQL). Ranges is in name => range format. | object({…})
| | null
| |
-| [regions](variables.tf#L193) | Region definitions. | object({…})
| | {…}
| |
-| [service_accounts](variables.tf#L205) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
-| [vpn_onprem_primary_config](variables.tf#L219) | VPN gateway configuration for onprem interconnection in the primary region. | object({…})
| | null
| |
-| [vpn_onprem_secondary_config](variables.tf#L262) | VPN gateway configuration for onprem interconnection in the secondary region. | object({…})
| | null
| |
-| [zones](variables.tf#L305) | Zones in which NVAs are deployed. | list(string)
| | ["b", "c"]
| |
+| [psa_ranges](variables.tf#L172) | IP ranges used for Private Service Access (e.g. CloudSQL). Ranges is in name => range format. | object({…})
| | null
| |
+| [regions](variables.tf#L189) | Region definitions. | object({…})
| | {…}
| |
+| [service_accounts](variables.tf#L201) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
+| [vpn_onprem_primary_config](variables.tf#L215) | VPN gateway configuration for onprem interconnection in the primary region. | object({…})
| | null
| |
+| [vpn_onprem_secondary_config](variables.tf#L258) | VPN gateway configuration for onprem interconnection in the secondary region. | object({…})
| | null
| |
+| [zones](variables.tf#L301) | Zones in which NVAs are deployed. | list(string)
| | ["b", "c"]
| |
## Outputs
diff --git a/fast/stages/2-networking-e-nva-bgp/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-e-nva-bgp/data/subnets/dev/dev-dataplatform-ew1.yaml
index cdb41e3f..1a8596b0 100644
--- a/fast/stages/2-networking-e-nva-bgp/data/subnets/dev/dev-dataplatform-ew1.yaml
+++ b/fast/stages/2-networking-e-nva-bgp/data/subnets/dev/dev-dataplatform-ew1.yaml
@@ -4,5 +4,5 @@ region: europe-west1
description: Default subnet for dev Data Platform
ip_cidr_range: 10.127.48.0/24
secondary_ip_ranges:
- pods: 100.64.0.0/24
+ pods: 100.64.0.0/16
services: 100.64.1.0/24
diff --git a/fast/stages/2-networking-e-nva-bgp/landing.tf b/fast/stages/2-networking-e-nva-bgp/landing.tf
index ab6c94eb..07331717 100644
--- a/fast/stages/2-networking-e-nva-bgp/landing.tf
+++ b/fast/stages/2-networking-e-nva-bgp/landing.tf
@@ -55,7 +55,9 @@ module "landing-untrusted-vpc" {
logging = false
}
create_googleapis_routes = null
- data_folder = "${var.factories_config.data_dir}/subnets/landing-untrusted"
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/landing-untrusted"
+ }
}
module "landing-untrusted-firewall" {
@@ -111,7 +113,9 @@ 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"
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/landing-trusted"
+ }
dns_policy = {
inbound = true
}
diff --git a/fast/stages/2-networking-e-nva-bgp/spoke-dev.tf b/fast/stages/2-networking-e-nva-bgp/spoke-dev.tf
index 0c70b550..56b65e39 100644
--- a/fast/stages/2-networking-e-nva-bgp/spoke-dev.tf
+++ b/fast/stages/2-networking-e-nva-bgp/spoke-dev.tf
@@ -45,11 +45,13 @@ module "dev-spoke-project" {
}
module "dev-spoke-vpc" {
- source = "../../../modules/net-vpc"
- project_id = module.dev-spoke-project.project_id
- name = "dev-spoke-0"
- mtu = 1500
- data_folder = "${var.factories_config.data_dir}/subnets/dev"
+ source = "../../../modules/net-vpc"
+ project_id = module.dev-spoke-project.project_id
+ name = "dev-spoke-0"
+ mtu = 1500
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/dev"
+ }
delete_default_routes_on_create = true
psa_config = try(var.psa_ranges.dev, null)
# Set explicit routes for googleapis; send everything else to NVAs
diff --git a/fast/stages/2-networking-e-nva-bgp/spoke-prod.tf b/fast/stages/2-networking-e-nva-bgp/spoke-prod.tf
index c0ba4414..6ae49dee 100644
--- a/fast/stages/2-networking-e-nva-bgp/spoke-prod.tf
+++ b/fast/stages/2-networking-e-nva-bgp/spoke-prod.tf
@@ -44,11 +44,13 @@ module "prod-spoke-project" {
}
module "prod-spoke-vpc" {
- source = "../../../modules/net-vpc"
- project_id = module.prod-spoke-project.project_id
- name = "prod-spoke-0"
- mtu = 1500
- data_folder = "${var.factories_config.data_dir}/subnets/prod"
+ source = "../../../modules/net-vpc"
+ project_id = module.prod-spoke-project.project_id
+ name = "prod-spoke-0"
+ mtu = 1500
+ factories_config = {
+ subnets_folder = "${var.factories_config.data_dir}/subnets/prod"
+ }
delete_default_routes_on_create = true
psa_config = try(var.psa_ranges.prod, null)
# Set explicit routes for googleapis; send everything else to NVAs
diff --git a/fast/stages/2-networking-e-nva-bgp/variables.tf b/fast/stages/2-networking-e-nva-bgp/variables.tf
index b8773041..a784fda3 100644
--- a/fast/stages/2-networking-e-nva-bgp/variables.tf
+++ b/fast/stages/2-networking-e-nva-bgp/variables.tf
@@ -173,18 +173,14 @@ variable "psa_ranges" {
description = "IP ranges used for Private Service Access (e.g. CloudSQL). Ranges is in name => range format."
type = object({
dev = object({
- ranges = map(string)
- routes = object({
- export = bool
- import = bool
- })
+ ranges = map(string)
+ export_routes = optional(bool, false)
+ import_routes = optional(bool, false)
})
prod = object({
- ranges = map(string)
- routes = object({
- export = bool
- import = bool
- })
+ ranges = map(string)
+ export_routes = optional(bool, false)
+ import_routes = optional(bool, false)
})
})
default = null
diff --git a/fast/stages/2-security/README.md b/fast/stages/2-security/README.md
index e28aac7b..9d47bdaf 100644
--- a/fast/stages/2-security/README.md
+++ b/fast/stages/2-security/README.md
@@ -284,13 +284,12 @@ Some references that might be useful in setting up this stage:
-
## Files
| name | description | modules | resources |
|---|---|---|---|
-| [core-dev.tf](./core-dev.tf) | None | kms
· project
| google_project_iam_member
|
-| [core-prod.tf](./core-prod.tf) | None | kms
· project
| google_project_iam_member
|
+| [core-dev.tf](./core-dev.tf) | None | kms
· project
| |
+| [core-prod.tf](./core-prod.tf) | None | kms
· project
| |
| [main.tf](./main.tf) | Module-level locals and resources. | | |
| [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object
· local_file
|
| [variables.tf](./variables.tf) | Module variables. | | |
@@ -303,17 +302,16 @@ Some references that might be useful in setting up this stage:
| [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
|
+| [organization](variables.tf#L97) | Organization details. | object({…})
| ✓ | | 0-bootstrap
|
+| [prefix](variables.tf#L113) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 0-bootstrap
|
+| [service_accounts](variables.tf#L124) | 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 definitions. | map(object({…}))
| | {}
| |
-| [vpc_sc_ingress_policies](variables.tf#L171) | VPC SC ingress policy definitions. | map(object({…}))
| | {}
| |
-| [vpc_sc_perimeters](variables.tf#L192) | VPC SC regular perimeter definitions. | object({…})
| | {}
| |
+| [kms_keys](variables.tf#L61) | KMS keys to create, keyed by name. | map(object({…}))
| | {}
| |
+| [outputs_location](variables.tf#L107) | 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#L135) | VPC SC access level definitions. | map(object({…}))
| | {}
| |
+| [vpc_sc_egress_policies](variables.tf#L164) | VPC SC egress policy definitions. | map(object({…}))
| | {}
| |
+| [vpc_sc_ingress_policies](variables.tf#L184) | VPC SC ingress policy definitions. | map(object({…}))
| | {}
| |
+| [vpc_sc_perimeters](variables.tf#L205) | VPC SC regular perimeter definitions. | object({…})
| | {}
| |
## Outputs
@@ -322,5 +320,4 @@ Some references that might be useful in setting up this stage:
| [kms_keys](outputs.tf#L59) | KMS key ids. | | |
| [stage_perimeter_projects](outputs.tf#L64) | Security project numbers. They can be added to perimeter resources. | | |
| [tfvars](outputs.tf#L74) | Terraform variable files for the following stages. | ✓ | |
-
diff --git a/fast/stages/2-security/core-dev.tf b/fast/stages/2-security/core-dev.tf
index 1b494947..6f71318d 100644
--- a/fast/stages/2-security/core-dev.tf
+++ b/fast/stages/2-security/core-dev.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,11 +16,11 @@
locals {
dev_kms_restricted_admins = [
- for sa in compact([
+ for sa in distinct(compact([
var.service_accounts.data-platform-dev,
var.service_accounts.project-factory-dev,
var.service_accounts.project-factory-prod
- ]) : "serviceAccount:${sa}"
+ ])) : "serviceAccount:${sa}"
]
}
@@ -33,6 +33,12 @@ module "dev-sec-project" {
iam = {
"roles/cloudkms.viewer" = local.dev_kms_restricted_admins
}
+ iam_bindings_additive = {
+ for member in local.dev_kms_restricted_admins :
+ "kms_restricted_admin.${member}" => merge(local.kms_restricted_admin_template, {
+ member = member
+ })
+ }
labels = { environment = "dev", team = "security" }
services = local.project_services
}
@@ -45,30 +51,5 @@ module "dev-sec-kms" {
location = each.key
name = "dev-${each.key}"
}
- # rename to `key_iam` to switch to authoritative bindings
- key_iam = {
- for k, v in local.kms_locations_keys[each.key] : k => v.iam
- }
keys = local.kms_locations_keys[each.key]
}
-
-# TODO(ludo): add support for conditions to Fabric modules
-
-resource "google_project_iam_member" "dev_key_admin_delegated" {
- for_each = toset(local.dev_kms_restricted_admins)
- project = module.dev-sec-project.project_id
- role = "roles/cloudkms.admin"
- member = each.key
- condition {
- title = "kms_sa_delegated_grants"
- description = "Automation service account delegated grants."
- expression = format(
- "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s]) && resource.type == 'cloudkms.googleapis.com/CryptoKey'",
- join(",", formatlist("'%s'", [
- "roles/cloudkms.cryptoKeyEncrypterDecrypter",
- "roles/cloudkms.cryptoKeyEncrypterDecrypterViaDelegation"
- ]))
- )
- }
- depends_on = [module.dev-sec-project]
-}
diff --git a/fast/stages/2-security/core-prod.tf b/fast/stages/2-security/core-prod.tf
index 559ff32f..1d536249 100644
--- a/fast/stages/2-security/core-prod.tf
+++ b/fast/stages/2-security/core-prod.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,10 +16,10 @@
locals {
prod_kms_restricted_admins = [
- for sa in compact([
+ for sa in distinct(compact([
var.service_accounts.data-platform-prod,
var.service_accounts.project-factory-prod
- ]) : "serviceAccount:${sa}"
+ ])) : "serviceAccount:${sa}"
]
}
@@ -32,6 +32,12 @@ module "prod-sec-project" {
iam = {
"roles/cloudkms.viewer" = local.prod_kms_restricted_admins
}
+ iam_bindings_additive = {
+ for member in local.prod_kms_restricted_admins :
+ "kms_restricted_admin.${member}" => merge(local.kms_restricted_admin_template, {
+ member = member
+ })
+ }
labels = { environment = "prod", team = "security" }
services = local.project_services
}
@@ -44,30 +50,5 @@ module "prod-sec-kms" {
location = each.key
name = "prod-${each.key}"
}
- # rename to `key_iam` to switch to authoritative bindings
- key_iam = {
- for k, v in local.kms_locations_keys[each.key] : k => v.iam
- }
keys = local.kms_locations_keys[each.key]
}
-
-# TODO(ludo): add support for conditions to Fabric modules
-
-resource "google_project_iam_member" "prod_key_admin_delegated" {
- for_each = toset(local.prod_kms_restricted_admins)
- project = module.prod-sec-project.project_id
- role = "roles/cloudkms.admin"
- member = each.key
- condition {
- title = "kms_sa_delegated_grants"
- description = "Automation service account delegated grants."
- expression = format(
- "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s]) && resource.type == 'cloudkms.googleapis.com/CryptoKey'",
- join(",", formatlist("'%s'", [
- "roles/cloudkms.cryptoKeyEncrypterDecrypter",
- "roles/cloudkms.cryptoKeyEncrypterDecrypterViaDelegation"
- ]))
- )
- }
- depends_on = [module.prod-sec-project]
-}
diff --git a/fast/stages/2-security/main.tf b/fast/stages/2-security/main.tf
index 13078d12..70799011 100644
--- a/fast/stages/2-security/main.tf
+++ b/fast/stages/2-security/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,28 +15,36 @@
*/
locals {
- kms_keys = {
- for k, v in var.kms_keys : k => {
- iam = coalesce(v.iam, {})
- labels = coalesce(v.labels, {})
- locations = (
- v.locations == null
- ? var.kms_defaults.locations
- : v.locations
- )
- rotation_period = (
- v.rotation_period == null
- ? var.kms_defaults.rotation_period
- : v.rotation_period
+ # additive IAM binding for delegated KMS admins
+ kms_restricted_admin_template = {
+ role = "roles/cloudkms.admin"
+ condition = {
+ title = "kms_sa_delegated_grants"
+ description = "Automation service account delegated grants."
+ expression = format(
+ <<-EOT
+ api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s]) &&
+ resource.type == 'cloudkms.googleapis.com/CryptoKey'
+ EOT
+ , join(",", formatlist("'%s'", [
+ "roles/cloudkms.cryptoKeyEncrypterDecrypter",
+ "roles/cloudkms.cryptoKeyEncrypterDecrypterViaDelegation"
+ ]))
)
}
}
+
+ # list of locations with keys
kms_locations = distinct(flatten([
- for k, v in local.kms_keys : v.locations
+ for k, v in var.kms_keys : v.locations
]))
+ # map { location -> { key_name -> key_details } }
kms_locations_keys = {
- for loc in local.kms_locations : loc => {
- for k, v in local.kms_keys : k => v if contains(v.locations, loc)
+ for loc in local.kms_locations :
+ loc => {
+ for k, v in var.kms_keys :
+ k => v
+ if contains(v.locations, loc)
}
}
project_services = [
diff --git a/fast/stages/2-security/variables.tf b/fast/stages/2-security/variables.tf
index f798de78..fa439c8c 100644
--- a/fast/stages/2-security/variables.tf
+++ b/fast/stages/2-security/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.
@@ -58,27 +58,40 @@ variable "groups" {
}
}
-variable "kms_defaults" {
- description = "Defaults used for KMS keys."
- type = object({
- locations = list(string)
- rotation_period = string
- })
- default = {
- locations = ["europe", "europe-west1", "europe-west3", "global"]
- rotation_period = "7776000s"
- }
-}
-
variable "kms_keys" {
- description = "KMS keys to create, keyed by name. Null attributes will be interpolated with defaults."
+ description = "KMS keys to create, keyed by name."
type = map(object({
- iam = map(list(string))
- labels = map(string)
- locations = list(string)
- rotation_period = string
+ rotation_period = optional(string, "7776000s")
+ labels = optional(map(string))
+ locations = optional(list(string), ["europe", "europe-west1", "europe-west3", "global"])
+ purpose = optional(string, "ENCRYPT_DECRYPT")
+ skip_initial_version_creation = optional(bool, false)
+ version_template = optional(object({
+ algorithm = string
+ protection_level = optional(string, "SOFTWARE")
+ }))
+
+ iam = optional(map(list(string)), {})
+ iam_bindings = optional(map(object({
+ members = list(string)
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
+ iam_bindings_additive = optional(map(object({
+ member = string
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
}))
- default = {}
+ default = {}
+ nullable = false
}
variable "organization" {
diff --git a/fast/stages/3-gke-multitenant/dev/README.md b/fast/stages/3-gke-multitenant/dev/README.md
index 23572297..f099c10b 100644
--- a/fast/stages/3-gke-multitenant/dev/README.md
+++ b/fast/stages/3-gke-multitenant/dev/README.md
@@ -163,21 +163,21 @@ Leave all these variables unset (or set to `null`) to disable fleet management.
|---|---|:---:|:---:|:---:|:---:|
| [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#L159) | 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#L174) | Host project for the shared VPC. | object({…})
| ✓ | | 2-networking
|
-| [prefix](variables.tf#L227) | Prefix used for resources that need unique names. | string
| ✓ | | |
-| [vpc_self_links](variables.tf#L243) | 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#L96) | Config management features enabled on specific sets of member clusters, in config name => [cluster name] format. | map(list(string))
| | {}
| |
-| [fleet_configmanagement_templates](variables.tf#L104) | Sets of config management configurations that can be applied to member clusters, in config name => {options} format. | map(object({…}))
| | {}
| |
-| [fleet_features](variables.tf#L139) | Enable and configure fleet features. Set to null to disable GKE Hub if fleet workload identity is not used. | object({…})
| | null
| |
-| [fleet_workload_identity](variables.tf#L152) | Use Fleet Workload Identity for clusters. Enables GKE Hub if set to true. | bool
| | false
| |
-| [group_iam](variables.tf#L167) | 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#L182) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
| |
-| [labels](variables.tf#L189) | Project-level labels. | map(string)
| | {}
| |
-| [nodepools](variables.tf#L195) | Nodepools configuration. Refer to the gke-nodepool module for type details. | map(map(object({…})))
| | {}
| |
-| [outputs_location](variables.tf#L221) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string
| | null
| |
-| [project_services](variables.tf#L236) | Additional project services to enable. | list(string)
| | []
| |
+| [folder_ids](variables.tf#L174) | 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#L189) | Host project for the shared VPC. | object({…})
| ✓ | | 2-networking
|
+| [prefix](variables.tf#L242) | Prefix used for resources that need unique names. | string
| ✓ | | |
+| [vpc_self_links](variables.tf#L258) | Self link for the shared VPC. | object({…})
| ✓ | | 2-networking
|
+| [clusters](variables.tf#L42) | Clusters configuration. Refer to the gke-cluster-standard module for type details. | map(object({…}))
| | {}
| |
+| [fleet_configmanagement_clusters](variables.tf#L111) | Config management features enabled on specific sets of member clusters, in config name => [cluster name] format. | map(list(string))
| | {}
| |
+| [fleet_configmanagement_templates](variables.tf#L119) | Sets of config management configurations that can be applied to member clusters, in config name => {options} format. | map(object({…}))
| | {}
| |
+| [fleet_features](variables.tf#L154) | Enable and configure fleet features. Set to null to disable GKE Hub if fleet workload identity is not used. | object({…})
| | null
| |
+| [fleet_workload_identity](variables.tf#L167) | Use Fleet Workload Identity for clusters. Enables GKE Hub if set to true. | bool
| | false
| |
+| [group_iam](variables.tf#L182) | 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#L197) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
| |
+| [labels](variables.tf#L204) | Project-level labels. | map(string)
| | {}
| |
+| [nodepools](variables.tf#L210) | Nodepools configuration. Refer to the gke-nodepool module for type details. | map(map(object({…})))
| | {}
| |
+| [outputs_location](variables.tf#L236) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string
| | null
| |
+| [project_services](variables.tf#L251) | Additional project services to enable. | list(string)
| | []
| |
## Outputs
diff --git a/fast/stages/3-gke-multitenant/dev/variables.tf b/fast/stages/3-gke-multitenant/dev/variables.tf
index 11e32ed6..831f828b 100644
--- a/fast/stages/3-gke-multitenant/dev/variables.tf
+++ b/fast/stages/3-gke-multitenant/dev/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.
@@ -40,7 +40,7 @@ variable "billing_account" {
}
variable "clusters" {
- description = "Clusters configuration. Refer to the gke-cluster module for type details."
+ description = "Clusters configuration. Refer to the gke-cluster-standard module for type details."
type = map(object({
cluster_autoscaling = optional(any)
description = optional(string)
@@ -68,9 +68,24 @@ variable "clusters" {
max_pods_per_node = optional(number, 110)
min_master_version = optional(string)
monitoring_config = optional(object({
- enable_components = optional(list(string), ["SYSTEM_COMPONENTS"])
- managed_prometheus = optional(bool)
- }))
+ enable_system_metrics = optional(bool, true)
+
+ # (Optional) control plane metrics
+ enable_api_server_metrics = optional(bool, false)
+ enable_controller_manager_metrics = optional(bool, false)
+ enable_scheduler_metrics = optional(bool, false)
+
+ # (Optional) kube state metrics
+ enable_daemonset_metrics = optional(bool, false)
+ enable_deployment_metrics = optional(bool, false)
+ enable_hpa_metrics = optional(bool, false)
+ enable_pod_metrics = optional(bool, false)
+ enable_statefulset_metrics = optional(bool, false)
+ enable_storage_metrics = optional(bool, false)
+
+ # Google Cloud Managed Service for Prometheus
+ enable_managed_prometheus = optional(bool, true)
+ }), {})
node_locations = optional(list(string))
private_cluster_config = optional(any)
release_channel = optional(string)
@@ -82,9 +97,9 @@ variable "clusters" {
services = string
}))
secondary_range_names = optional(object({
- pods = string
- services = string
- }), { pods = "pods", services = "services" })
+ pods = optional(string, "pods")
+ services = optional(string, "services")
+ }))
master_authorized_ranges = optional(map(string))
master_ipv4_cidr_block = optional(string)
})
diff --git a/fast/stages/3-project-factory/dev/README.md b/fast/stages/3-project-factory/dev/README.md
index 2073e759..4c1fe75d 100644
--- a/fast/stages/3-project-factory/dev/README.md
+++ b/fast/stages/3-project-factory/dev/README.md
@@ -55,7 +55,7 @@ gcloud alpha storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/2-security.auto.
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.
-Besides the values above, the project factory is drive by data files, with one file per project.
+Besides the values above, the project factory is driven by data files which closely follow the variables exposed by the [project module](../../../../modules/project/), with one file per project. Please refer to the underlying [project factory blueprint](../../../../blueprints/factories/project-factory/) documentation for details on the format.
Once the configuration is complete, run the project factory with:
@@ -79,8 +79,8 @@ terraform apply
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
| [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
|
-| [factory_data](variables.tf#L32) | Project data from either YAML files or externally parsed data. | object({…})
| ✓ | | |
-| [prefix](variables.tf#L48) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 0-bootstrap
|
+| [prefix](variables.tf#L51) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 0-bootstrap
|
+| [factory_data](variables.tf#L32) | Project data from either YAML files or externally parsed data. | object({…})
| | {…}
| |
## Outputs
diff --git a/fast/stages/3-project-factory/dev/data/defaults.yaml b/fast/stages/3-project-factory/dev/data/defaults.yaml
deleted file mode 100644
index e52bb132..00000000
--- a/fast/stages/3-project-factory/dev/data/defaults.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-# skip boilerplate check
-
-billing_account_id: 012345-67890A-BCDEF0
-
-# [opt] Setup for billing alerts
-billing_alert:
- amount: 1000
- thresholds:
- current: [0.5, 0.8]
- forecasted: [0.5, 0.8]
- credit_treatment: INCLUDE_ALL_CREDITS
-
-# [opt] Contacts for billing alerts and important notifications
-essential_contacts: ["team-contacts@example.com"]
-
-# [opt] Labels set for all projects
-labels:
- environment: dev
- department: accounting
- application: example-app
- foo: bar
-
-# [opt] Additional notification channels for billing
-notification_channels: []
diff --git a/fast/stages/3-project-factory/dev/data/projects/project.yaml.sample b/fast/stages/3-project-factory/dev/data/projects/project.yaml.sample
deleted file mode 100644
index 5311019d..00000000
--- a/fast/stages/3-project-factory/dev/data/projects/project.yaml.sample
+++ /dev/null
@@ -1,103 +0,0 @@
-# skip boilerplate check
-
-# [opt] Billing account id - overrides default if set
-billing_account_id: 012345-67890A-BCDEF0
-
-# [opt] Billing alerts config - overrides default if set
-billing_alert:
- amount: 10
- thresholds:
- current:
- - 0.5
- - 0.8
- forecasted: []
- credit_treatment: INCLUDE_ALL_CREDITS
-
-# [opt] DNS zones to be created as children of the environment_dns_zone defined in defaults
-dns_zones:
- - lorem
- - ipsum
-
-# [opt] Contacts for billing alerts and important notifications
-essential_contacts:
- - team-a-contacts@example.com
-
-# Folder the project will be created as children of
-folder_id: folders/012345678901
-
-# [opt] Authoritative IAM bindings in group => [roles] format
-group_iam:
- test-team-foobar@fast-lab-0.gcp-pso-italy.net:
- - roles/compute.admin
-
-# [opt] Authoritative IAM bindings in role => [principals] format
-# Generally used to grant roles to service accounts external to the project
-iam:
- roles/compute.admin:
- - serviceAccount:service-account
-
-# [opt] Service robots and keys they will be assigned as cryptoKeyEncrypterDecrypter
-# in service => [keys] format
-kms_service_agents:
- compute: [key1, key2]
- storage: [key1, key2]
-
-# [opt] Labels for the project - merged with the ones defined in defaults
-labels:
- environment: dev
-
-# [opt] Org policy overrides defined at project level
-org_policies:
- compute.disableGuestAttributesAccess:
- rules:
- - enforce: true
- compute.trustedImageProjects:
- rules:
- - allow:
- values:
- - projects/fast-dev-iac-core-0
- compute.vmExternalIpAccess:
- rules:
- - deny:
- all: true
-
-# [opt] Service account to create for the project and their roles on the project
-# in name => [roles] format
-service_accounts:
- another-service-account:
- - roles/compute.admin
- my-service-account:
- - roles/compute.admin
-
-# [opt] APIs to enable on the project.
-services:
- - storage.googleapis.com
- - stackdriver.googleapis.com
- - compute.googleapis.com
-
-# [opt] Roles to assign to the service identities in service => [roles] format
-service_identities_iam:
- compute:
- - roles/storage.objectViewer
-
- # [opt] VPC setup.
- # If set enables the `compute.googleapis.com` service and configures
- # service project attachment
-vpc:
- # [opt] If set, enables the container API
- gke_setup:
- # Grants "roles/container.hostServiceAgentUser" to the container robot if set
- enable_host_service_agent: false
-
- # Grants "roles/compute.securityAdmin" to the container robot if set
- enable_security_admin: true
-
- # Host project the project will be service project of
- host_project: fast-dev-net-spoke-0
-
- # [opt] Subnets in the host project where principals will be granted networkUser
- # in region/subnet-name => [principals]
- subnets_iam:
- europe-west1/dev-default-ew1:
- - user:foobar@example.com
- - serviceAccount:service-account1
diff --git a/fast/stages/3-project-factory/dev/data/projects/test-project.yaml b/fast/stages/3-project-factory/dev/data/projects/test-project.yaml
new file mode 100644
index 00000000..dfe34e6c
--- /dev/null
+++ b/fast/stages/3-project-factory/dev/data/projects/test-project.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.
+
+labels:
+ team: team-0
+parent: folders/1234567890
+services:
+- compute.googleapis.com
+- storage.googleapis.com
diff --git a/fast/stages/3-project-factory/dev/main.tf b/fast/stages/3-project-factory/dev/main.tf
index 261351ca..4f23b492 100644
--- a/fast/stages/3-project-factory/dev/main.tf
+++ b/fast/stages/3-project-factory/dev/main.tf
@@ -31,7 +31,7 @@ module "projects" {
]
}
data_overrides = {
- prefix = var.prefix
+ prefix = "${var.prefix}-dev"
}
factory_data = var.factory_data
}
diff --git a/fast/stages/3-project-factory/dev/variables.tf b/fast/stages/3-project-factory/dev/variables.tf
index d004aeb8..c7165e3c 100644
--- a/fast/stages/3-project-factory/dev/variables.tf
+++ b/fast/stages/3-project-factory/dev/variables.tf
@@ -36,6 +36,9 @@ variable "factory_data" {
data_path = optional(string)
})
nullable = false
+ default = {
+ data_path = "data/projects"
+ }
validation {
condition = (
(var.factory_data.data != null ? 1 : 0) +
@@ -49,7 +52,6 @@ variable "prefix" {
# tfdoc:variable:source 0-bootstrap
description = "Prefix used for resources that need unique names. Use 9 characters or less."
type = string
-
validation {
condition = try(length(var.prefix), 0) < 10
error_message = "Use a maximum of 9 characters for prefix."
diff --git a/modules/__docs/20230816-iam-refactor.md b/modules/__docs/20230816-iam-refactor.md
index 438252ac..46916657 100644
--- a/modules/__docs/20230816-iam-refactor.md
+++ b/modules/__docs/20230816-iam-refactor.md
@@ -6,6 +6,7 @@
## Status
Implemented in [#1595](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1595).
+Authoritative bindings type changed as per [#1622](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/issues/1622).
## Context
@@ -39,15 +40,18 @@ The new `iam_bindings` variable will look like this:
```hcl
variable "iam_bindings" {
- description = "Authoritative IAM bindings with support for conditions, in {ROLE => { members = [], condition = {}}} format."
+ description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary."
type = map(object({
- members = list(string)
+ members = list(string)
+ role = string
condition = optional(object({
expression = string
title = string
description = optional(string)
}))
}))
+ nullable = false
+ default = {}
}
```
@@ -94,8 +98,8 @@ The new variable will closely follow the type of the authoritative `iam_bindings
variable "iam_bindings_additive" {
description = "Additive IAM bindings with support for conditions, in {KEY => { role = ROLE, members = [], condition = {}}} format."
type = map(object({
- member = string
- role = string
+ member = string
+ role = string
condition = optional(object({
expression = string
title = string
@@ -128,3 +132,213 @@ This brings several advantages over the previous handling of IAM:
### Blueprints
A few data blueprints that leverage `iam_additive` have been refactored to use the new variable. This is most notable in data blueprints, where extra files have been added to the more complex examples like data foundations, to abstract IAM bindings in a way similar to what is described above for FAST.
+
+## Implementation
+
+The following sections provide a template for IAM-related variables and resources to ensure a consistent implementation of IAM across the repository. Use these code snippets to add IAM support to your module.
+
+### Top-level module IAM
+
+Use this template if your module manages a single instance of a given resource (e.g. a KMS keyring).
+
+```terraform
+# variables.tf
+
+variable "iam" {
+ description = "IAM bindings in {ROLE => [MEMBERS]} format. Mutually exclusive with the access_* variables used for basic roles."
+ type = map(list(string))
+ default = {}
+ nullable = false
+}
+
+variable "iam_bindings" {
+ description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary."
+ type = map(object({
+ members = list(string)
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ }))
+ default = {}
+ nullable = false
+}
+
+variable "iam_bindings_additive" {
+ description = "Keyring individual additive IAM bindings. Keys are arbitrary."
+ type = map(object({
+ member = string
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ }))
+ default = {}
+ nullable = false
+}
+```
+
+```terraform
+# iam.tf
+
+resource "google_RESOURCE_TYPE_iam_binding" "authoritative" {
+ for_each = var.iam
+ role = each.key
+ members = each.value
+ // add extra attributes (e.g. resource id)
+}
+
+resource "google_RESOURCE_TYPE_iam_binding" "bindings" {
+ for_each = var.iam_bindings
+ role = each.value.role
+ members = each.value.members
+ // add extra attributes (e.g. resource id)
+
+ dynamic "condition" {
+ for_each = each.value.condition == null ? [] : [""]
+ content {
+ expression = each.value.condition.expression
+ title = each.value.condition.title
+ description = each.value.condition.description
+ }
+ }
+}
+
+resource "google_RESOURCE_TYPE_iam_member" "bindings" {
+ for_each = var.iam_bindings_additive
+ role = each.value.role
+ member = each.value.member
+ // add extra attributes (e.g. resource id)
+
+ dynamic "condition" {
+ for_each = each.value.condition == null ? [] : [""]
+ content {
+ expression = each.value.condition.expression
+ title = each.value.condition.title
+ description = each.value.condition.description
+ }
+ }
+}
+```
+
+### Sub-resources IAM
+
+Use this template if your module manages multiple instances of a resource (e.g. keys in KMS keyring).
+
+```terraform
+# variables.tf
+variable "sub_resources" {
+ type = map(object({
+ # sub-resource configuration here
+
+ iam = optional(map(list(string)), {})
+ iam_bindings = optional(map(object({
+ members = list(string)
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
+ iam_bindings_additive = optional(map(object({
+ member = string
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
+ }))
+ default = {}
+ nullable = false
+}
+```
+
+```terraform
+# iam.tf
+locals {
+ SUB_RESOURCE_iam = flatten([
+ for k, v in var.SUB_RESOURCEs : [
+ for role, members in v.iam : {
+ key = k
+ role = role
+ members = members
+ }
+ ]
+ ])
+ SUB_RESOURCE_iam_bindings = merge([
+ for k, v in var.SUB_RESOURCEs : {
+ for binding_key, data in v.iam_bindings :
+ binding_key => {
+ SUB_RESOURCE = k
+ role = data.role
+ members = data.members
+ condition = data.condition
+ }
+ }
+ ]...)
+ SUB_RESOURCE_iam_bindings_additive = merge([
+ for k, v in var.subresources : {
+ for binding_key, data in v.iam_bindings_additive :
+ binding_key => {
+ SUB_RESOURCE = k
+ role = data.role
+ member = data.member
+ condition = data.condition
+ }
+ }
+ ]...)
+}
+```
+
+```terraform
+# iam.tf
+
+resource "google_SUB_RESOURCE_iam_binding" "authoritative" {
+ for_each = {
+ for binding in local.SUB_RESOURCE_iam :
+ "${binding.key}.${binding.role}" => binding
+ }
+ role = each.value.role
+ members = each.value.members
+ // add extra attributes (e.g. sub resource id)
+}
+
+resource "google_SUB_RESOURCE_iam_binding" "bindings" {
+ for_each = local.SUB_RESOURCE_iam_bindings
+ role = each.value.role
+ members = each.value.members
+ // add extra attributes (e.g. sub resource id)
+
+ dynamic "condition" {
+ for_each = each.value.condition == null ? [] : [""]
+ content {
+ expression = each.value.condition.expression
+ title = each.value.condition.title
+ description = each.value.condition.description
+ }
+ }
+}
+
+resource "google_SUB_RESOURCE_iam_member" "members" {
+ for_each = local.SUB_RESOURCE_iam_bindings_additive
+ role = each.value.role
+ member = each.value.member
+ // add extra attributes (e.g. sub resource id)
+
+ dynamic "condition" {
+ for_each = each.value.condition == null ? [] : [""]
+ content {
+ expression = each.value.condition.expression
+ title = each.value.condition.title
+ description = each.value.condition.description
+ }
+ }
+}
+
+```
diff --git a/modules/__experimental/net-neg/versions.tf b/modules/__experimental/net-neg/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/__experimental/net-neg/versions.tf
+++ b/modules/__experimental/net-neg/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/alloydb-instance/versions.tf b/modules/alloydb-instance/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/alloydb-instance/versions.tf
+++ b/modules/alloydb-instance/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/api-gateway/versions.tf b/modules/api-gateway/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/api-gateway/versions.tf
+++ b/modules/api-gateway/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/apigee/README.md b/modules/apigee/README.md
index 67daa729..cb99a34a 100644
--- a/modules/apigee/README.md
+++ b/modules/apigee/README.md
@@ -2,7 +2,121 @@
This module simplifies the creation of a Apigee resources (organization, environment groups, environment group attachments, environments, instances and instance attachments).
-## Example
+## Examples
+
+
+- [Examples](#examples)
+ - [Minimal example (CLOUD)](#minimal-example-cloud)
+ - [Minimal example with existing organization (CLOUD)](#minimal-example-with-existing-organization-cloud)
+ - [Disable VPC Peering (CLOUD)](#disable-vpc-peering-cloud)
+ - [All resources (CLOUD)](#all-resources-cloud)
+ - [All resources (HYBRID control plane)](#all-resources-hybrid-control-plane)
+ - [New environment group](#new-environment-group)
+ - [New environment](#new-environment)
+ - [New instance (VPC Peering Provisioning Mode)](#new-instance-vpc-peering-provisioning-mode)
+ - [New instance (Non VPC Peering Provisioning Mode)](#new-instance-non-vpc-peering-provisioning-mode)
+ - [New endpoint attachment](#new-endpoint-attachment)
+ - [Apigee add-ons](#apigee-add-ons)
+- [Variables](#variables)
+- [Outputs](#outputs)
+
+
+### Minimal example (CLOUD)
+
+This example shows how to create to create an Apigee organization and deploy instance in it.
+
+```hcl
+module "apigee" {
+ source = "./fabric/modules/apigee"
+ project_id = var.project_id
+ organization = {
+ display_name = "Apigee"
+ billing_type = "PAYG"
+ analytics_region = "europe-west1"
+ authorized_network = var.vpc.id
+ runtime_type = "CLOUD"
+ }
+ envgroups = {
+ prod = ["prod.example.com"]
+ }
+ environments = {
+ apis-prod = {
+ display_name = "APIs prod"
+ description = "APIs Prod"
+ envgroups = ["prod"]
+ }
+ }
+ instances = {
+ europe-west1 = {
+ environments = ["apis-prod"]
+ runtime_ip_cidr_range = "10.32.0.0/22"
+ troubleshooting_ip_cidr_range = "10.64.0.0/28"
+ }
+ }
+}
+# tftest modules=1 resources=6 inventory=minimal-cloud.yaml
+```
+
+### Minimal example with existing organization (CLOUD)
+
+This example shows how to create to work with an existing organization in the project. Note that in this case we don't specify the IP ranges for the instance, so it requests and allocates an available /22 and /28 CIDR block from Service Networking to deploy the instance.
+
+```hcl
+module "apigee" {
+ source = "./fabric/modules/apigee"
+ project_id = var.project_id
+ envgroups = {
+ prod = ["prod.example.com"]
+ }
+ environments = {
+ apis-prod = {
+ display_name = "APIs prod"
+ envgroups = ["prod"]
+ }
+ }
+ instances = {
+ europe-west1 = {
+ environments = ["apis-prod"]
+ }
+ }
+}
+# tftest modules=1 resources=5 inventory=minimal-cloud-no-org.yaml
+```
+
+### Disable VPC Peering (CLOUD)
+
+When a new Apigee organization is created, it is automatically peered to the authorized network. You can prevent this from happening by using the `disable_vpc_peering` key in the `organization` variable, as shown below:
+
+
+```hcl
+module "apigee" {
+ source = "./fabric/modules/apigee"
+ project_id = var.project_id
+ organization = {
+ display_name = "Apigee"
+ billing_type = "PAYG"
+ analytics_region = "europe-west1"
+ runtime_type = "CLOUD"
+ disable_vpc_peering = true
+ }
+ envgroups = {
+ prod = ["prod.example.com"]
+ }
+ environments = {
+ apis-prod = {
+ display_name = "APIs prod"
+ envgroups = ["prod"]
+ }
+ }
+ instances = {
+ europe-west1 = {
+ environments = ["apis-prod"]
+ }
+ }
+}
+# tftest modules=1 resources=6 inventory=no-peering.yaml
+```
+
### All resources (CLOUD)
@@ -28,13 +142,11 @@ module "apigee" {
display_name = "APIs test"
description = "APIs Test"
envgroups = ["test"]
- regions = ["europe-west1"]
}
apis-prod = {
display_name = "APIs prod"
description = "APIs prod"
envgroups = ["prod"]
- regions = ["europe-west3"]
iam = {
"roles/viewer" = ["group:devops@myorg.com"]
}
@@ -44,10 +156,12 @@ module "apigee" {
europe-west1 = {
runtime_ip_cidr_range = "10.0.4.0/22"
troubleshooting_ip_cidr_range = "10.1.1.0.0/28"
+ environments = ["apis-test"]
}
europe-west3 = {
runtime_ip_cidr_range = "10.0.8.0/22"
troubleshooting_ip_cidr_range = "10.1.16.0/28"
+ environments = ["apis-prod"]
enable_nat = true
}
}
@@ -129,7 +243,7 @@ module "apigee" {
# tftest modules=1 resources=1
```
-### New instance
+### New instance (VPC Peering Provisioning Mode)
```hcl
module "apigee" {
@@ -145,6 +259,28 @@ module "apigee" {
# tftest modules=1 resources=1
```
+### New instance (Non VPC Peering Provisioning Mode)
+
+```hcl
+module "apigee" {
+ source = "./fabric/modules/apigee"
+ project_id = "my-project"
+ organization = {
+ display_name = "My Organization"
+ description = "My Organization"
+ runtime_type = "CLOUD"
+ billing_type = "Pay-as-you-go"
+ database_encryption_key = "123456789"
+ analytics_region = "europe-west1"
+ disable_vpc_peering = true
+ }
+ instances = {
+ europe-west1 = {}
+ }
+}
+# tftest modules=1 resources=2
+```
+
### New endpoint attachment
Endpoint attachments allow to implement [Apigee southbound network patterns](https://cloud.google.com/apigee/docs/api-platform/architecture/southbound-networking-patterns-endpoints#create-the-psc-attachments).
@@ -180,6 +316,7 @@ module "apigee" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
+<<<<<<< HEAD
| [project_id](variables.tf#L97) | Project ID. | string
| ✓ | |
| [addons_config](variables.tf#L17) | Addons configuration. | object({…})
| | null
|
| [endpoint_attachments](variables.tf#L29) | Endpoint attachments. | map(object({…}))
| | {}
|
@@ -187,6 +324,15 @@ module "apigee" {
| [environments](variables.tf#L46) | Environments. | map(object({…}))
| | {}
|
| [instances](variables.tf#L65) | Instances ([REGION] => [INSTANCE]). | map(object({…}))
| | {}
|
| [organization](variables.tf#L82) | Apigee organization. If set to null the organization must already exist. | object({…})
| | null
|
+=======
+| [project_id](variables.tf#L117) | Project ID. | string
| ✓ | |
+| [addons_config](variables.tf#L17) | Addons configuration. | object({…})
| | null
|
+| [endpoint_attachments](variables.tf#L29) | Endpoint attachments. | map(object({…}))
| | {}
|
+| [envgroups](variables.tf#L39) | Environment groups (NAME => [HOSTNAMES]). | map(list(string))
| | {}
|
+| [environments](variables.tf#L46) | Environments. | map(object({…}))
| | {}
|
+| [instances](variables.tf#L64) | Instances ([REGION] => [INSTANCE]). | map(object({…}))
| | {}
|
+| [organization](variables.tf#L89) | Apigee organization. If set to null the organization must already exist. | object({…})
| | null
|
+>>>>>>> master
## Outputs
diff --git a/modules/apigee/main.tf b/modules/apigee/main.tf
index 84c121db..cd1f7197 100644
--- a/modules/apigee/main.tf
+++ b/modules/apigee/main.tf
@@ -28,6 +28,7 @@ resource "google_apigee_organization" "organization" {
runtime_type = var.organization.runtime_type
runtime_database_encryption_key_name = var.organization.database_encryption_key
retention = var.organization.retention
+ disable_vpc_peering = var.organization.disable_vpc_peering
}
resource "google_apigee_envgroup" "envgroups" {
@@ -85,13 +86,17 @@ resource "google_apigee_environment_iam_binding" "binding" {
}
resource "google_apigee_instance" "instances" {
- for_each = var.instances
- name = coalesce(each.value.name, "instance-${each.key}")
- display_name = each.value.display_name
- description = each.value.description
- location = each.key
- org_id = local.org_id
- ip_range = "${each.value.runtime_ip_cidr_range},${each.value.troubleshooting_ip_cidr_range}"
+ for_each = var.instances
+ name = coalesce(each.value.name, "instance-${each.key}")
+ display_name = each.value.display_name
+ description = each.value.description
+ location = each.key
+ org_id = local.org_id
+ ip_range = (
+ compact([each.value.runtime_ip_cidr_range, each.value.troubleshooting_ip_cidr_range]) == []
+ ? null
+ : join(",", compact([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
}
@@ -109,12 +114,12 @@ resource "google_apigee_nat_address" "apigee_nat" {
resource "google_apigee_instance_attachment" "instance_attachments" {
for_each = merge(concat([for k1, v1 in var.instances : {
for v2 in coalesce(v1.environments, []) :
- "${k1}-${v2}" => {
+ "${v2}-${k1}" => {
instance = k1
environment = v2
}
}])...)
- instance_id = google_apigee_instance.instances[each.value.region].id
+ instance_id = google_apigee_instance.instances[each.value.instance].id
environment = try(google_apigee_environment.environments[each.value.environment].name,
"${local.org_id}/environments/${each.value.environment}")
}
@@ -127,7 +132,7 @@ resource "google_apigee_endpoint_attachment" "endpoint_attachments" {
service_attachment = each.value.service_attachment
}
-resource "google_apigee_addons_config" "test_organization" {
+resource "google_apigee_addons_config" "addons_config" {
for_each = toset(var.addons_config == null ? [] : [""])
org = local.org_name
addons_config {
diff --git a/modules/apigee/variables.tf b/modules/apigee/variables.tf
index db09c28c..3109956d 100644
--- a/modules/apigee/variables.tf
+++ b/modules/apigee/variables.tf
@@ -64,16 +64,26 @@ variable "environments" {
variable "instances" {
description = "Instances ([REGION] => [INSTANCE])."
type = map(object({
+ name = optional(string)
display_name = optional(string)
name = optional(string)
description = optional(string, "Terraform-managed")
- runtime_ip_cidr_range = string
- troubleshooting_ip_cidr_range = string
+ runtime_ip_cidr_range = optional(string)
+ troubleshooting_ip_cidr_range = optional(string)
disk_encryption_key = optional(string)
consumer_accept_list = optional(list(string))
environments = optional(list(string))
enable_nat = optional(bool, false)
+ environments = optional(list(string))
}))
+ validation {
+ condition = alltrue([
+ for k, v in var.instances :
+ # has troubleshooting_ip => has runtime_ip
+ v.runtime_ip_cidr_range != null || v.troubleshooting_ip_cidr_range == null
+ ])
+ error_message = "Using a troubleshooting range requires specifying a runtime range too."
+ }
default = {}
nullable = false
}
@@ -89,7 +99,20 @@ variable "organization" {
database_encryption_key = optional(string)
analytics_region = optional(string, "europe-west1")
retention = optional(string)
+ disable_vpc_peering = optional(bool, false)
})
+ validation {
+ condition = var.organization == null || (
+ try(var.organization.runtime_type, null) == "CLOUD" || !try(var.organization.disable_vpc_peering, false)
+ )
+ error_message = "Disabling the VPC peering can only be done in organization using the CLOUD runtime."
+ }
+ validation {
+ condition = var.organization == null || (
+ try(var.organization.authorized_network, null) == null || !try(var.organization.disable_vpc_peering, false)
+ )
+ error_message = "Disabling the VPC peering is mutually exclusive with authorized_network."
+ }
default = null
}
diff --git a/modules/apigee/versions.tf b/modules/apigee/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/apigee/versions.tf
+++ b/modules/apigee/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/artifact-registry/versions.tf b/modules/artifact-registry/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/artifact-registry/versions.tf
+++ b/modules/artifact-registry/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/bigquery-dataset/versions.tf b/modules/bigquery-dataset/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/bigquery-dataset/versions.tf
+++ b/modules/bigquery-dataset/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/bigtable-instance/versions.tf b/modules/bigtable-instance/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/bigtable-instance/versions.tf
+++ b/modules/bigtable-instance/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/billing-budget/versions.tf b/modules/billing-budget/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/billing-budget/versions.tf
+++ b/modules/billing-budget/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/binauthz/versions.tf b/modules/binauthz/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/binauthz/versions.tf
+++ b/modules/binauthz/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/__need_fixing/onprem/versions.tf b/modules/cloud-config-container/__need_fixing/onprem/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloud-config-container/__need_fixing/onprem/versions.tf
+++ b/modules/cloud-config-container/__need_fixing/onprem/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/coredns/versions.tf b/modules/cloud-config-container/coredns/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloud-config-container/coredns/versions.tf
+++ b/modules/cloud-config-container/coredns/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/cos-generic-metadata/versions.tf b/modules/cloud-config-container/cos-generic-metadata/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloud-config-container/cos-generic-metadata/versions.tf
+++ b/modules/cloud-config-container/cos-generic-metadata/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/envoy-traffic-director/versions.tf b/modules/cloud-config-container/envoy-traffic-director/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloud-config-container/envoy-traffic-director/versions.tf
+++ b/modules/cloud-config-container/envoy-traffic-director/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/mysql/versions.tf b/modules/cloud-config-container/mysql/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloud-config-container/mysql/versions.tf
+++ b/modules/cloud-config-container/mysql/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/nginx-tls/versions.tf b/modules/cloud-config-container/nginx-tls/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloud-config-container/nginx-tls/versions.tf
+++ b/modules/cloud-config-container/nginx-tls/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/nginx/versions.tf b/modules/cloud-config-container/nginx/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloud-config-container/nginx/versions.tf
+++ b/modules/cloud-config-container/nginx/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/simple-nva/versions.tf b/modules/cloud-config-container/simple-nva/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloud-config-container/simple-nva/versions.tf
+++ b/modules/cloud-config-container/simple-nva/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloud-config-container/squid/versions.tf b/modules/cloud-config-container/squid/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloud-config-container/squid/versions.tf
+++ b/modules/cloud-config-container/squid/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloud-function-v1/versions.tf b/modules/cloud-function-v1/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloud-function-v1/versions.tf
+++ b/modules/cloud-function-v1/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloud-function-v2/versions.tf b/modules/cloud-function-v2/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloud-function-v2/versions.tf
+++ b/modules/cloud-function-v2/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloud-identity-group/versions.tf b/modules/cloud-identity-group/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloud-identity-group/versions.tf
+++ b/modules/cloud-identity-group/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloud-run/versions.tf b/modules/cloud-run/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloud-run/versions.tf
+++ b/modules/cloud-run/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/cloudsql-instance/README.md b/modules/cloudsql-instance/README.md
index 00cf5ded..74afa419 100644
--- a/modules/cloudsql-instance/README.md
+++ b/modules/cloudsql-instance/README.md
@@ -116,13 +116,12 @@ module "kms" {
location = var.region
}
keys = {
- key-sql = null
- }
- key_iam = {
key-sql = {
- "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [
- "serviceAccount:${module.project.service_accounts.robots.sqladmin}"
- ]
+ iam = {
+ "roles/cloudkms.cryptoKeyEncrypterDecrypter" = [
+ "serviceAccount:${module.project.service_accounts.robots.sqladmin}"
+ ]
+ }
}
}
}
diff --git a/modules/cloudsql-instance/versions.tf b/modules/cloudsql-instance/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/cloudsql-instance/versions.tf
+++ b/modules/cloudsql-instance/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/compute-mig/README.md b/modules/compute-mig/README.md
index b281c2e3..5e3dbd8e 100644
--- a/modules/compute-mig/README.md
+++ b/modules/compute-mig/README.md
@@ -389,7 +389,6 @@ module "nginx-mig" {
# tftest modules=2 resources=3 inventory=stateful.yaml
```
-
## Variables
| name | description | type | required | default |
@@ -400,7 +399,7 @@ module "nginx-mig" {
| [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
|
+| [autoscaler_config](variables.tf#L35) | Optional autoscaler configuration. | object({…})
| | null
|
| [default_version_name](variables.tf#L83) | Name used for the default version. | string
| | "default"
|
| [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
|
@@ -422,5 +421,4 @@ module "nginx-mig" {
| [group_manager](outputs.tf#L26) | Instance group resource. | |
| [health_check](outputs.tf#L35) | Auto-created health-check resource. | |
| [id](outputs.tf#L44) | Fully qualified group manager id. | |
-
diff --git a/modules/compute-mig/autoscaler.tf b/modules/compute-mig/autoscaler.tf
index b8bd0acc..c0f77491 100644
--- a/modules/compute-mig/autoscaler.tf
+++ b/modules/compute-mig/autoscaler.tf
@@ -35,6 +35,7 @@ resource "google_compute_autoscaler" "default" {
max_replicas = var.autoscaler_config.max_replicas
min_replicas = var.autoscaler_config.min_replicas
cooldown_period = var.autoscaler_config.cooldown_period
+ mode = var.autoscaler_config.mode
dynamic "scale_down_control" {
for_each = local.as_scaling.down == null ? [] : [""]
@@ -138,6 +139,7 @@ resource "google_compute_region_autoscaler" "default" {
max_replicas = var.autoscaler_config.max_replicas
min_replicas = var.autoscaler_config.min_replicas
cooldown_period = var.autoscaler_config.cooldown_period
+ mode = var.autoscaler_config.mode
dynamic "scale_down_control" {
for_each = local.as_scaling.down == null ? [] : [""]
diff --git a/modules/compute-mig/variables.tf b/modules/compute-mig/variables.tf
index 30f2ce96..20864d18 100644
--- a/modules/compute-mig/variables.tf
+++ b/modules/compute-mig/variables.tf
@@ -61,8 +61,8 @@ variable "autoscaler_config" {
}))
metrics = optional(list(object({
name = string
- type = string # GAUGE, DELTA_PER_SECOND, DELTA_PER_MINUTE
- target_value = number
+ type = optional(string) # GAUGE, DELTA_PER_SECOND, DELTA_PER_MINUTE
+ target_value = optional(number)
single_instance_assignment = optional(number)
time_series_filter = optional(string)
})))
diff --git a/modules/compute-mig/versions.tf b/modules/compute-mig/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/compute-mig/versions.tf
+++ b/modules/compute-mig/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/compute-vm/main.tf b/modules/compute-vm/main.tf
index 0ca16257..e79cc18c 100644
--- a/modules/compute-vm/main.tf
+++ b/modules/compute-vm/main.tf
@@ -187,7 +187,7 @@ resource "google_compute_instance" "default" {
source = (
config.value.source_type == "attach"
? config.value.source
- : google_compute_region_disk.disks[config.key].name
+ : google_compute_region_disk.disks[config.key].id
)
}
}
diff --git a/modules/compute-vm/versions.tf b/modules/compute-vm/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/compute-vm/versions.tf
+++ b/modules/compute-vm/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/container-registry/versions.tf b/modules/container-registry/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/container-registry/versions.tf
+++ b/modules/container-registry/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/data-catalog-policy-tag/README.md b/modules/data-catalog-policy-tag/README.md
index b08a9feb..8a464784 100644
--- a/modules/data-catalog-policy-tag/README.md
+++ b/modules/data-catalog-policy-tag/README.md
@@ -79,17 +79,17 @@ module "cmn-dc" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L76) | Name of this taxonomy. | string
| ✓ | |
-| [project_id](variables.tf#L91) | GCP project id. |
| ✓ | |
+| [name](variables.tf#L77) | Name of this taxonomy. | string
| ✓ | |
+| [project_id](variables.tf#L92) | GCP project id. |
| ✓ | |
| [activated_policy_types](variables.tf#L17) | A list of policy types that are activated for this taxonomy. | list(string)
| | ["FINE_GRAINED_ACCESS_CONTROL"]
|
| [description](variables.tf#L23) | Description of this taxonomy. | string
| | "Taxonomy - Terraform managed"
|
| [group_iam](variables.tf#L29) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string))
| | {}
|
| [iam](variables.tf#L35) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
-| [iam_bindings](variables.tf#L41) | Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}. | map(object({…}))
| | {}
|
-| [iam_bindings_additive](variables.tf#L55) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
-| [location](variables.tf#L70) | Data Catalog Taxonomy location. | string
| | "eu"
|
-| [prefix](variables.tf#L81) | Optional prefix used to generate project id and name. | string
| | null
|
-| [tags](variables.tf#L95) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(object({…}))
| | {}
|
+| [iam_bindings](variables.tf#L41) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [iam_bindings_additive](variables.tf#L56) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [location](variables.tf#L71) | Data Catalog Taxonomy location. | string
| | "eu"
|
+| [prefix](variables.tf#L82) | Optional prefix used to generate project id and name. | string
| | null
|
+| [tags](variables.tf#L96) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(object({…}))
| | {}
|
## Outputs
diff --git a/modules/data-catalog-policy-tag/iam.tf b/modules/data-catalog-policy-tag/iam.tf
index 268c0c58..06c30763 100644
--- a/modules/data-catalog-policy-tag/iam.tf
+++ b/modules/data-catalog-policy-tag/iam.tf
@@ -53,7 +53,7 @@ resource "google_data_catalog_taxonomy_iam_binding" "bindings" {
provider = google-beta
for_each = var.iam_bindings
taxonomy = google_data_catalog_taxonomy.default.id
- role = each.key
+ role = each.value.role
members = each.value.members
dynamic "condition" {
for_each = each.value.condition == null ? [] : [""]
diff --git a/modules/data-catalog-policy-tag/variables.tf b/modules/data-catalog-policy-tag/variables.tf
index b0df313d..0fef9e7b 100644
--- a/modules/data-catalog-policy-tag/variables.tf
+++ b/modules/data-catalog-policy-tag/variables.tf
@@ -39,9 +39,10 @@ variable "iam" {
}
variable "iam_bindings" {
- description = "Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}."
+ description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary."
type = map(object({
members = list(string)
+ role = string
condition = optional(object({
expression = string
title = string
diff --git a/modules/data-catalog-policy-tag/versions.tf b/modules/data-catalog-policy-tag/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/data-catalog-policy-tag/versions.tf
+++ b/modules/data-catalog-policy-tag/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/datafusion/versions.tf b/modules/datafusion/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/datafusion/versions.tf
+++ b/modules/datafusion/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/dataplex-datascan/README.md b/modules/dataplex-datascan/README.md
index 1c950184..4116732f 100644
--- a/modules/dataplex-datascan/README.md
+++ b/modules/dataplex-datascan/README.md
@@ -431,9 +431,9 @@ module "dataplex-datascan" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [data](variables.tf#L17) | The data source for DataScan. The source can be either a Dataplex `entity` or a BigQuery `resource`. | object({…})
| ✓ | |
-| [name](variables.tf#L156) | Name of Dataplex Scan. | string
| ✓ | |
-| [project_id](variables.tf#L167) | The ID of the project where the Dataplex DataScan will be created. | string
| ✓ | |
-| [region](variables.tf#L172) | Region for the Dataplex DataScan. | string
| ✓ | |
+| [name](variables.tf#L157) | Name of Dataplex Scan. | string
| ✓ | |
+| [project_id](variables.tf#L168) | The ID of the project where the Dataplex DataScan will be created. | string
| ✓ | |
+| [region](variables.tf#L173) | Region for the Dataplex DataScan. | string
| ✓ | |
| [data_profile_spec](variables.tf#L29) | DataProfileScan related setting. Variable descriptions are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataProfileSpec. | object({…})
| | null
|
| [data_quality_spec](variables.tf#L38) | DataQualityScan related setting. Variable descriptions are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataQualitySpec. | object({…})
| | null
|
| [data_quality_spec_file](variables.tf#L80) | Path to a YAML file containing DataQualityScan related setting. Input content can use either camelCase or snake_case. Variables description are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataQualitySpec. | object({…})
| | null
|
@@ -441,11 +441,11 @@ module "dataplex-datascan" {
| [execution_schedule](variables.tf#L94) | Schedule DataScan to run periodically based on a cron schedule expression. If not specified, the DataScan is created with `on_demand` schedule, which means it will not run until the user calls `dataScans.run` API. | string
| | null
|
| [group_iam](variables.tf#L100) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string))
| | {}
|
| [iam](variables.tf#L107) | Dataplex DataScan IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
-| [iam_bindings](variables.tf#L114) | Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}. | map(object({…}))
| | {}
|
-| [iam_bindings_additive](variables.tf#L128) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
-| [incremental_field](variables.tf#L143) | The unnested field (of type Date or Timestamp) that contains values which monotonically increase over time. If not specified, a data scan will run for all data in the table. | string
| | null
|
-| [labels](variables.tf#L149) | Resource labels. | map(string)
| | {}
|
-| [prefix](variables.tf#L161) | Optional prefix used to generate Dataplex DataScan ID. | string
| | null
|
+| [iam_bindings](variables.tf#L114) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [iam_bindings_additive](variables.tf#L129) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [incremental_field](variables.tf#L144) | The unnested field (of type Date or Timestamp) that contains values which monotonically increase over time. If not specified, a data scan will run for all data in the table. | string
| | null
|
+| [labels](variables.tf#L150) | Resource labels. | map(string)
| | {}
|
+| [prefix](variables.tf#L162) | Optional prefix used to generate Dataplex DataScan ID. | string
| | null
|
## Outputs
diff --git a/modules/dataplex-datascan/iam.tf b/modules/dataplex-datascan/iam.tf
index 9a496ff1..9ed59144 100644
--- a/modules/dataplex-datascan/iam.tf
+++ b/modules/dataplex-datascan/iam.tf
@@ -44,7 +44,7 @@ resource "google_dataplex_datascan_iam_binding" "bindings" {
project = google_dataplex_datascan.datascan.project
location = google_dataplex_datascan.datascan.location
data_scan_id = google_dataplex_datascan.datascan.data_scan_id
- role = each.key
+ role = each.value.role
members = each.value.members
dynamic "condition" {
for_each = each.value.condition == null ? [] : [""]
diff --git a/modules/dataplex-datascan/variables.tf b/modules/dataplex-datascan/variables.tf
index 4e6b2bb1..a13cdc55 100644
--- a/modules/dataplex-datascan/variables.tf
+++ b/modules/dataplex-datascan/variables.tf
@@ -112,9 +112,10 @@ variable "iam" {
}
variable "iam_bindings" {
- description = "Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}."
+ description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary."
type = map(object({
members = list(string)
+ role = string
condition = optional(object({
expression = string
title = string
diff --git a/modules/dataplex-datascan/versions.tf b/modules/dataplex-datascan/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/dataplex-datascan/versions.tf
+++ b/modules/dataplex-datascan/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/dataplex/versions.tf b/modules/dataplex/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/dataplex/versions.tf
+++ b/modules/dataplex/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/dataproc/README.md b/modules/dataproc/README.md
index aa532671..5cd220cb 100644
--- a/modules/dataproc/README.md
+++ b/modules/dataproc/README.md
@@ -146,17 +146,17 @@ module "processing-dp-cluster" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L234) | Cluster name. | string
| ✓ | |
-| [project_id](variables.tf#L249) | Project ID. | string
| ✓ | |
-| [region](variables.tf#L254) | Dataproc region. | string
| ✓ | |
+| [name](variables.tf#L235) | Cluster name. | string
| ✓ | |
+| [project_id](variables.tf#L250) | Project ID. | string
| ✓ | |
+| [region](variables.tf#L255) | Dataproc region. | string
| ✓ | |
| [dataproc_config](variables.tf#L17) | Dataproc cluster config. | object({…})
| | {}
|
| [group_iam](variables.tf#L185) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string))
| | {}
|
| [iam](variables.tf#L192) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
-| [iam_bindings](variables.tf#L199) | Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}. | map(object({…}))
| | {}
|
-| [iam_bindings_additive](variables.tf#L213) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
-| [labels](variables.tf#L228) | The resource labels for instance to use to annotate any related underlying resources, such as Compute Engine VMs. | map(string)
| | {}
|
-| [prefix](variables.tf#L239) | Optional prefix used to generate project id and name. | string
| | null
|
-| [service_account](variables.tf#L259) | Service account to set on the Dataproc cluster. | string
| | null
|
+| [iam_bindings](variables.tf#L199) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [iam_bindings_additive](variables.tf#L214) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [labels](variables.tf#L229) | The resource labels for instance to use to annotate any related underlying resources, such as Compute Engine VMs. | map(string)
| | {}
|
+| [prefix](variables.tf#L240) | Optional prefix used to generate project id and name. | string
| | null
|
+| [service_account](variables.tf#L260) | Service account to set on the Dataproc cluster. | string
| | null
|
## Outputs
diff --git a/modules/dataproc/iam.tf b/modules/dataproc/iam.tf
index fba2eca9..ef0428d1 100644
--- a/modules/dataproc/iam.tf
+++ b/modules/dataproc/iam.tf
@@ -46,7 +46,7 @@ resource "google_dataproc_cluster_iam_binding" "bindings" {
project = var.project_id
cluster = google_dataproc_cluster.cluster.name
region = var.region
- role = each.key
+ role = each.value.role
members = each.value.members
dynamic "condition" {
for_each = each.value.condition == null ? [] : [""]
diff --git a/modules/dataproc/variables.tf b/modules/dataproc/variables.tf
index 49f4fa90..8b77c5b9 100644
--- a/modules/dataproc/variables.tf
+++ b/modules/dataproc/variables.tf
@@ -197,9 +197,10 @@ variable "iam" {
}
variable "iam_bindings" {
- description = "Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}."
+ description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary."
type = map(object({
members = list(string)
+ role = string
condition = optional(object({
expression = string
title = string
diff --git a/modules/dataproc/versions.tf b/modules/dataproc/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/dataproc/versions.tf
+++ b/modules/dataproc/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/dns-response-policy/versions.tf b/modules/dns-response-policy/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/dns-response-policy/versions.tf
+++ b/modules/dns-response-policy/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/dns/README.md b/modules/dns/README.md
index cdfff0e3..5b293768 100644
--- a/modules/dns/README.md
+++ b/modules/dns/README.md
@@ -140,17 +140,16 @@ module "public-dns" {
# tftest modules=1 resources=4 inventory=public-zone.yaml
```
-
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L33) | Zone name, must be unique within the project. | string
| ✓ | |
-| [project_id](variables.tf#L38) | Project id for the zone. | string
| ✓ | |
-| [description](variables.tf#L21) | Domain description. | string
| | "Terraform managed."
|
-| [iam](variables.tf#L27) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string))
| | null
|
-| [recordsets](variables.tf#L43) | Map of DNS recordsets in \"type name\" => {ttl, [records]} format. | map(object({…}))
| | {}
|
-| [zone_config](variables.tf#L78) | DNS zone configuration. | object({…})
| | null
|
+| [name](variables.tf#L29) | Zone name, must be unique within the project. | string
| ✓ | |
+| [project_id](variables.tf#L34) | Project id for the zone. | string
| ✓ | |
+| [description](variables.tf#L17) | Domain description. | string
| | "Terraform managed."
|
+| [iam](variables.tf#L23) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string))
| | null
|
+| [recordsets](variables.tf#L39) | Map of DNS recordsets in \"type name\" => {ttl, [records]} format. | map(object({…}))
| | {}
|
+| [zone_config](variables.tf#L74) | DNS zone configuration. | object({…})
| | null
|
## Outputs
@@ -162,5 +161,4 @@ module "public-dns" {
| [name](outputs.tf#L32) | The DNS zone name. | |
| [name_servers](outputs.tf#L37) | The DNS zone name servers. | |
| [zone](outputs.tf#L42) | DNS zone resource. | |
-
diff --git a/modules/dns/variables.tf b/modules/dns/variables.tf
index 9c2bf545..08395ba0 100644
--- a/modules/dns/variables.tf
+++ b/modules/dns/variables.tf
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-###############################################################################
-# zone variables #
-###############################################################################
-
variable "description" {
description = "Domain description."
type = string
diff --git a/modules/dns/versions.tf b/modules/dns/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/dns/versions.tf
+++ b/modules/dns/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/endpoints/versions.tf b/modules/endpoints/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/endpoints/versions.tf
+++ b/modules/endpoints/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/folder/README.md b/modules/folder/README.md
index b4f41601..65661210 100644
--- a/modules/folder/README.md
+++ b/modules/folder/README.md
@@ -290,17 +290,17 @@ module "folder" {
| [folder_create](variables.tf#L33) | Create folder. When set to false, uses id to reference an existing folder. | bool
| | true
|
| [group_iam](variables.tf#L39) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string))
| | {}
|
| [iam](variables.tf#L46) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
-| [iam_bindings](variables.tf#L53) | Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}. | map(object({…}))
| | {}
|
-| [iam_bindings_additive](variables.tf#L67) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
-| [id](variables.tf#L82) | Folder ID in case you use folder_create=false. | string
| | null
|
-| [logging_data_access](variables.tf#L88) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string)))
| | {}
|
-| [logging_exclusions](variables.tf#L103) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string)
| | {}
|
-| [logging_sinks](variables.tf#L110) | Logging sinks to create for the organization. | map(object({…}))
| | {}
|
-| [name](variables.tf#L140) | Folder name. | string
| | null
|
-| [org_policies](variables.tf#L146) | Organization policies applied to this folder keyed by policy name. | map(object({…}))
| | {}
|
-| [org_policies_data_path](variables.tf#L173) | Path containing org policies in YAML format. | string
| | null
|
-| [parent](variables.tf#L179) | Parent in folders/folder_id or organizations/org_id format. | string
| | null
|
-| [tag_bindings](variables.tf#L189) | Tag bindings for this folder, in key => tag value id format. | map(string)
| | null
|
+| [iam_bindings](variables.tf#L53) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [iam_bindings_additive](variables.tf#L68) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [id](variables.tf#L83) | Folder ID in case you use folder_create=false. | string
| | null
|
+| [logging_data_access](variables.tf#L89) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string)))
| | {}
|
+| [logging_exclusions](variables.tf#L104) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string)
| | {}
|
+| [logging_sinks](variables.tf#L111) | Logging sinks to create for the organization. | map(object({…}))
| | {}
|
+| [name](variables.tf#L141) | Folder name. | string
| | null
|
+| [org_policies](variables.tf#L147) | Organization policies applied to this folder keyed by policy name. | map(object({…}))
| | {}
|
+| [org_policies_data_path](variables.tf#L174) | Path containing org policies in YAML format. | string
| | null
|
+| [parent](variables.tf#L180) | Parent in folders/folder_id or organizations/org_id format. | string
| | null
|
+| [tag_bindings](variables.tf#L190) | Tag bindings for this folder, in key => tag value id format. | map(string)
| | null
|
## Outputs
diff --git a/modules/folder/iam.tf b/modules/folder/iam.tf
index 976e312c..20025b28 100644
--- a/modules/folder/iam.tf
+++ b/modules/folder/iam.tf
@@ -42,7 +42,7 @@ resource "google_folder_iam_binding" "authoritative" {
resource "google_folder_iam_binding" "bindings" {
for_each = var.iam_bindings
folder = local.folder.name
- role = each.key
+ role = each.value.role
members = each.value.members
dynamic "condition" {
for_each = each.value.condition == null ? [] : [""]
diff --git a/modules/folder/variables.tf b/modules/folder/variables.tf
index 619ee9c3..86efc215 100644
--- a/modules/folder/variables.tf
+++ b/modules/folder/variables.tf
@@ -51,9 +51,10 @@ variable "iam" {
}
variable "iam_bindings" {
- description = "Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}."
+ description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary."
type = map(object({
members = list(string)
+ role = string
condition = optional(object({
expression = string
title = string
diff --git a/modules/folder/versions.tf b/modules/folder/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/folder/versions.tf
+++ b/modules/folder/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/gcs/versions.tf b/modules/gcs/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/gcs/versions.tf
+++ b/modules/gcs/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/gcve-private-cloud/versions.tf b/modules/gcve-private-cloud/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/gcve-private-cloud/versions.tf
+++ b/modules/gcve-private-cloud/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/gke-cluster-autopilot/README.md b/modules/gke-cluster-autopilot/README.md
index da639066..b54588c8 100644
--- a/modules/gke-cluster-autopilot/README.md
+++ b/modules/gke-cluster-autopilot/README.md
@@ -1,10 +1,23 @@
-# GKE cluster Autopilot module
+# GKE Autopilot cluster module
-This module allows simplified creation and management of GKE Autopilot clusters. Some sensible defaults are set initially, in order to allow less verbose usage for most use cases.
+This module offers a way to create and manage Google Kubernetes Engine (GKE) [Autopilot clusters](https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-overview). With its sensible default settings based on best practices and authors' experience as Google Cloud practitioners, the module accommodates for many common use cases out-of-the-box, without having to rely on verbose configuration.
-## Example
+
+- [Examples](#examples)
+ - [GKE Autopilot cluster](#gke-autopilot-cluster)
+ - [Cloud DNS](#cloud-dns)
+ - [Logging configuration](#logging-configuration)
+ - [Monitoring configuration](#monitoring-configuration)
+ - [Backup for GKE](#backup-for-gke)
+- [Variables](#variables)
+- [Outputs](#outputs)
+
-### GKE Cluster
+## Examples
+
+### GKE Autopilot cluster
+
+This example shows how to [create a GKE cluster in Autopilot mode](https://cloud.google.com/kubernetes-engine/docs/how-to/creating-an-autopilot-cluster).
```hcl
module "cluster-1" {
@@ -37,7 +50,10 @@ module "cluster-1" {
### 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.
+> [!WARNING]
+> [Cloud DNS is the only DNS provider for Autopilot clusters](https://cloud.google.com/kubernetes-engine/docs/concepts/service-discovery#cloud_dns) running version `1.25.9-gke.400` and later, and version `1.26.4-gke.500` and later. It is [pre-configured](https://cloud.google.com/kubernetes-engine/docs/resources/autopilot-standard-feature-comparison#feature-comparison) for those clusters. The following example *only* applies to Autopilot clusters running *earlier* versions.
+
+This example shows how to [use Cloud DNS as a Kubernetes DNS provider](https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-dns).
```hcl
module "cluster-1" {
@@ -48,7 +64,7 @@ module "cluster-1" {
vpc_config = {
network = var.vpc.self_link
subnetwork = var.subnet.self_link
- secondary_range_names = { pods = "pods", services = "services" }
+ secondary_range_names = {} # use default names "pods" and "services"
}
enable_features = {
dns = {
@@ -63,11 +79,11 @@ module "cluster-1" {
### Logging configuration
-This example shows how to [collect logs for the Kubernetes control plane components](https://cloud.google.com/stackdriver/docs/solutions/gke/installing). The logs for these components are not collected by default.
-
-> **Note**
+> [!NOTE]
> System and workload logs collection is pre-configured for Autopilot clusters and cannot be disabled.
+This example shows how to [collect logs for the Kubernetes control plane components](https://cloud.google.com/stackdriver/docs/solutions/gke/installing). The logs for these components are not collected by default.
+
```hcl
module "cluster-1" {
source = "./fabric/modules/gke-cluster-autopilot"
@@ -75,8 +91,9 @@ module "cluster-1" {
name = "cluster-1"
location = "europe-west1"
vpc_config = {
- network = var.vpc.self_link
- subnetwork = var.subnet.self_link
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ secondary_range_names = {} # use default names "pods" and "services"
}
logging_config = {
enable_api_server_logs = true
@@ -89,36 +106,13 @@ module "cluster-1" {
### Monitoring configuration
-This example shows how to [configure collection of Kubernetes control plane metrics](https://cloud.google.com/stackdriver/docs/solutions/gke/managing-metrics#enable-control-plane-metrics). The metrics for these components are not collected by default.
+> [!NOTE]
+> [System metrics](https://cloud.google.com/stackdriver/docs/solutions/gke/managing-metrics#enable-system-metrics) collection is pre-configured for Autopilot clusters and cannot be disabled.
-> **Note**
-> System metrics collection is pre-configured for Autopilot clusters and cannot be disabled.
-
-> **Warning**
+> [!WARNING]
> GKE **workload metrics** is deprecated and removed in GKE 1.24 and later. Workload metrics is replaced by [Google Cloud Managed Service for Prometheus](https://cloud.google.com/stackdriver/docs/managed-prometheus), which is Google's recommended way to monitor Kubernetes applications by using Cloud Monitoring.
-```hcl
-module "cluster-1" {
- source = "./fabric/modules/gke-cluster-autopilot"
- project_id = var.project_id
- name = "cluster-1"
- location = "europe-west1"
- vpc_config = {
- network = var.vpc.self_link
- subnetwork = var.subnet.self_link
- }
- monitoring_config = {
- enable_api_server_metrics = true
- enable_controller_manager_metrics = true
- enable_scheduler_metrics = true
- }
-}
-# tftest modules=1 resources=1 inventory=monitoring-config-control-plane.yaml
-```
-
-### Backup for GKE
-
-This example shows how to [enable the Backup for GKE agent and configure a Backup Plan](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/concepts/backup-for-gke) for GKE Standard clusters.
+This example shows how to [configure collection of Kubernetes control plane metrics](https://cloud.google.com/stackdriver/docs/solutions/gke/managing-metrics#enable-control-plane-metrics). These metrics are optional and are not collected by default.
```hcl
module "cluster-1" {
@@ -129,7 +123,71 @@ module "cluster-1" {
vpc_config = {
network = var.vpc.self_link
subnetwork = var.subnet.self_link
- secondary_range_names = { pods = "pods", services = "services" }
+ secondary_range_names = {} # use default names "pods" and "services"
+ }
+ monitoring_config = {
+ enable_api_server_metrics = true
+ enable_controller_manager_metrics = true
+ enable_scheduler_metrics = true
+ }
+}
+# tftest modules=1 resources=1 inventory=monitoring-config-control-plane.yaml
+```
+
+The next example shows how to [configure collection of kube state metrics](https://cloud.google.com/stackdriver/docs/solutions/gke/managing-metrics#enable-ksm). These metrics are optional and are not collected by default.
+
+```hcl
+module "cluster-1" {
+ source = "./fabric/modules/gke-cluster-autopilot"
+ project_id = var.project_id
+ name = "cluster-1"
+ location = "europe-west1"
+ vpc_config = {
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ secondary_range_names = {} # use default names "pods" and "services"
+ }
+ monitoring_config = {
+ enable_daemonset_metrics = true
+ enable_deployment_metrics = true
+ enable_hpa_metrics = true
+ enable_pod_metrics = true
+ enable_statefulset_metrics = true
+ enable_storage_metrics = true
+ # Kube state metrics collection requires Google Cloud Managed Service for Prometheus,
+ # which is enabled by default.
+ # enable_managed_prometheus = true
+ }
+}
+# tftest modules=1 resources=1 inventory=monitoring-config-kube-state.yaml
+```
+
+The *control plane metrics* and *kube state metrics* collection can be configured in a single `monitoring_config` block.
+
+### Backup for GKE
+
+> [!NOTE]
+> Although Backup for GKE can be enabled as an add-on when configuring your GKE clusters, it is a separate service from GKE.
+
+[Backup for GKE](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/concepts/backup-for-gke) is a service for backing up and restoring workloads in GKE clusters. It has two components:
+
+* A [Google Cloud API](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/reference/rest) that serves as the control plane for the service.
+* A GKE add-on (the [Backup for GKE agent](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/concepts/backup-for-gke#agent_overview)) that must be enabled in each cluster for which you wish to perform backup and restore operations.
+
+Backup for GKE is supported in GKE Autopilot clusters with [some restrictions](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/concepts/about-autopilot).
+
+This example shows how to [enable Backup for GKE on a new Autopilot cluster](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/how-to/install#enable_on_a_new_cluster_optional) and [plan a set of backups](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/how-to/backup-plan).
+
+```hcl
+module "cluster-1" {
+ source = "./fabric/modules/gke-cluster-autopilot"
+ project_id = var.project_id
+ name = "cluster-1"
+ location = "europe-west1"
+ vpc_config = {
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ secondary_range_names = {}
}
backup_configs = {
enable_backup_agent = true
@@ -148,10 +206,10 @@ module "cluster-1" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [location](variables.tf#L110) | Autopilot cluster are always regional. | string
| ✓ | |
-| [name](variables.tf#L170) | Cluster name. | string
| ✓ | |
-| [project_id](variables.tf#L196) | Cluster project id. | string
| ✓ | |
-| [vpc_config](variables.tf#L224) | VPC-level configuration. | object({…})
| ✓ | |
+| [location](variables.tf#L110) | Autopilot clusters are always regional. | string
| ✓ | |
+| [name](variables.tf#L187) | Cluster name. | string
| ✓ | |
+| [project_id](variables.tf#L213) | Cluster project ID. | string
| ✓ | |
+| [vpc_config](variables.tf#L242) | VPC-level configuration. | object({…})
| ✓ | |
| [backup_configs](variables.tf#L17) | Configuration for Backup for GKE. | object({…})
| | {}
|
| [description](variables.tf#L37) | Cluster description. | string
| | null
|
| [enable_addons](variables.tf#L43) | Addons enabled in the cluster (true means enabled). | object({…})
| | {…}
|
@@ -161,12 +219,12 @@ module "cluster-1" {
| [logging_config](variables.tf#L115) | Logging configuration. | object({…})
| | {}
|
| [maintenance_config](variables.tf#L126) | Maintenance window configuration. | object({…})
| | {…}
|
| [min_master_version](variables.tf#L149) | Minimum version of the master, defaults to the version of the most recent official release. | string
| | null
|
-| [monitoring_config](variables.tf#L155) | Monitoring configuration. System metrics collection cannot be disabled for Autopilot clusters. Control plane metrics are optional. Google Cloud Managed Service for Prometheus is enabled by default. | object({…})
| | {}
|
-| [node_locations](variables.tf#L175) | Zones in which the cluster's nodes are located. | list(string)
| | []
|
-| [private_cluster_config](variables.tf#L182) | Private cluster configuration. | object({…})
| | null
|
-| [release_channel](variables.tf#L201) | Release channel for GKE upgrades. Clusters created in the Autopilot mode must use a release channel. Choose between \"RAPID\", \"REGULAR\", and \"STABLE\". | string
| | "REGULAR"
|
-| [service_account](variables.tf#L212) | The Google Cloud Platform Service Account to be used by the node VMs created by GKE Autopilot. | string
| | null
|
-| [tags](variables.tf#L218) | Network tags applied to nodes. | list(string)
| | null
|
+| [monitoring_config](variables.tf#L155) | Monitoring configuration. System metrics collection cannot be disabled. Control plane metrics are optional. Kube state metrics are optional. Google Cloud Managed Service for Prometheus is enabled by default. | object({…})
| | {}
|
+| [node_locations](variables.tf#L192) | Zones in which the cluster's nodes are located. | list(string)
| | []
|
+| [private_cluster_config](variables.tf#L199) | Private cluster configuration. | object({…})
| | null
|
+| [release_channel](variables.tf#L218) | Release channel for GKE upgrades. Clusters created in the Autopilot mode must use a release channel. Choose between \"RAPID\", \"REGULAR\", and \"STABLE\". | string
| | "REGULAR"
|
+| [service_account](variables.tf#L229) | The Google Cloud Platform Service Account to be used by the node VMs created by GKE Autopilot. | string
| | null
|
+| [tags](variables.tf#L235) | Network tags applied to nodes. | list(string)
| | []
|
## Outputs
@@ -175,7 +233,7 @@ module "cluster-1" {
| [ca_certificate](outputs.tf#L17) | Public certificate of the cluster (base64-encoded). | ✓ |
| [cluster](outputs.tf#L23) | Cluster resource. | ✓ |
| [endpoint](outputs.tf#L29) | Cluster endpoint. | |
-| [id](outputs.tf#L34) | Fully qualified cluster id. | |
+| [id](outputs.tf#L34) | Fully qualified cluster ID. | |
| [location](outputs.tf#L39) | Cluster location. | |
| [master_version](outputs.tf#L44) | Master version. | |
| [name](outputs.tf#L49) | Cluster name. | |
diff --git a/modules/gke-cluster-autopilot/main.tf b/modules/gke-cluster-autopilot/main.tf
index 330c4993..4ca8ee54 100644
--- a/modules/gke-cluster-autopilot/main.tf
+++ b/modules/gke-cluster-autopilot/main.tf
@@ -103,12 +103,19 @@ resource "google_container_cluster" "cluster" {
}
}
+ dynamic "gateway_api_config" {
+ for_each = var.enable_features.gateway_api ? [""] : []
+ content {
+ channel = "CHANNEL_STANDARD"
+ }
+ }
+
dynamic "ip_allocation_policy" {
for_each = var.vpc_config.secondary_range_blocks != null ? [""] : []
content {
cluster_ipv4_cidr_block = var.vpc_config.secondary_range_blocks.pods
services_ipv4_cidr_block = var.vpc_config.secondary_range_blocks.services
- stack_type = try(var.vpc_config.stack_type, null)
+ stack_type = var.vpc_config.stack_type
}
}
@@ -117,7 +124,7 @@ resource "google_container_cluster" "cluster" {
content {
cluster_secondary_range_name = var.vpc_config.secondary_range_names.pods
services_secondary_range_name = var.vpc_config.secondary_range_names.services
- stack_type = try(var.vpc_config.stack_type, null)
+ stack_type = var.vpc_config.stack_type
}
}
@@ -131,13 +138,6 @@ 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 = (
@@ -207,10 +207,17 @@ resource "google_container_cluster" "cluster" {
enable_components = toset(compact([
# System metrics collection cannot be disabled for Autopilot clusters.
"SYSTEM_COMPONENTS",
- # Control plane metrics.
+ # Control plane metrics:
var.monitoring_config.enable_api_server_metrics ? "APISERVER" : null,
var.monitoring_config.enable_controller_manager_metrics ? "CONTROLLER_MANAGER" : null,
var.monitoring_config.enable_scheduler_metrics ? "SCHEDULER" : null,
+ # Kube state metrics:
+ var.monitoring_config.enable_daemonset_metrics ? "DAEMONSET" : null,
+ var.monitoring_config.enable_deployment_metrics ? "DEPLOYMENT" : null,
+ var.monitoring_config.enable_hpa_metrics ? "HPA" : null,
+ var.monitoring_config.enable_pod_metrics ? "POD" : null,
+ var.monitoring_config.enable_statefulset_metrics ? "STATEFULSET" : null,
+ var.monitoring_config.enable_storage_metrics ? "STORAGE" : null,
]))
managed_prometheus {
enabled = var.monitoring_config.enable_managed_prometheus
@@ -231,6 +238,15 @@ resource "google_container_cluster" "cluster" {
}
}
+ dynamic "node_pool_auto_config" {
+ for_each = length(var.tags) > 0 ? [""] : []
+ content {
+ network_tags {
+ tags = toset(var.tags)
+ }
+ }
+ }
+
dynamic "private_cluster_config" {
for_each = (
var.private_cluster_config != null ? [""] : []
diff --git a/modules/gke-cluster-autopilot/outputs.tf b/modules/gke-cluster-autopilot/outputs.tf
index 029ab06a..7978e55b 100644
--- a/modules/gke-cluster-autopilot/outputs.tf
+++ b/modules/gke-cluster-autopilot/outputs.tf
@@ -32,7 +32,7 @@ output "endpoint" {
}
output "id" {
- description = "Fully qualified cluster id."
+ description = "Fully qualified cluster ID."
value = google_container_cluster.cluster.id
}
diff --git a/modules/gke-cluster-autopilot/variables.tf b/modules/gke-cluster-autopilot/variables.tf
index 52896bbd..24f8cd2b 100644
--- a/modules/gke-cluster-autopilot/variables.tf
+++ b/modules/gke-cluster-autopilot/variables.tf
@@ -108,7 +108,7 @@ variable "labels" {
}
variable "location" {
- description = "Autopilot cluster are always regional."
+ description = "Autopilot clusters are always regional."
type = string
}
@@ -153,18 +153,35 @@ variable "min_master_version" {
}
variable "monitoring_config" {
- description = "Monitoring configuration. System metrics collection cannot be disabled for Autopilot clusters. Control plane metrics are optional. Google Cloud Managed Service for Prometheus is enabled by default."
+ description = "Monitoring configuration. System metrics collection cannot be disabled. Control plane metrics are optional. Kube state metrics are optional. Google Cloud Managed Service for Prometheus is enabled by default."
type = object({
# Control plane metrics
enable_api_server_metrics = optional(bool, false)
enable_controller_manager_metrics = optional(bool, false)
enable_scheduler_metrics = optional(bool, false)
- # Google Cloud Managed Service for Prometheus
- # GKE Autopilot clusters running GKE version 1.25 or greater must have this on.
+ # Kube state metrics. Requires managed Prometheus. Requires provider version >= v4.82.0
+ enable_daemonset_metrics = optional(bool, false)
+ enable_deployment_metrics = optional(bool, false)
+ enable_hpa_metrics = optional(bool, false)
+ enable_pod_metrics = optional(bool, false)
+ enable_statefulset_metrics = optional(bool, false)
+ enable_storage_metrics = optional(bool, false)
+ # Google Cloud Managed Service for Prometheus. Autopilot clusters version >= 1.25 must have this on.
enable_managed_prometheus = optional(bool, true)
})
default = {}
nullable = false
+ validation {
+ condition = anytrue([
+ var.monitoring_config.enable_daemonset_metrics,
+ var.monitoring_config.enable_deployment_metrics,
+ var.monitoring_config.enable_hpa_metrics,
+ var.monitoring_config.enable_pod_metrics,
+ var.monitoring_config.enable_statefulset_metrics,
+ var.monitoring_config.enable_storage_metrics,
+ ]) ? var.monitoring_config.enable_managed_prometheus : true
+ error_message = "Kube state metrics collection requires Google Cloud Managed Service for Prometheus to be enabled."
+ }
}
variable "name" {
@@ -194,7 +211,7 @@ variable "private_cluster_config" {
}
variable "project_id" {
- description = "Cluster project id."
+ description = "Cluster project ID."
type = string
}
@@ -218,7 +235,8 @@ variable "service_account" {
variable "tags" {
description = "Network tags applied to nodes."
type = list(string)
- default = null
+ default = []
+ nullable = false
}
variable "vpc_config" {
@@ -232,9 +250,9 @@ variable "vpc_config" {
services = string
}))
secondary_range_names = optional(object({
- pods = string
- services = string
- }), { pods = "pods", services = "services" })
+ pods = optional(string, "pods")
+ services = optional(string, "services")
+ }))
master_authorized_ranges = optional(map(string))
stack_type = optional(string)
})
diff --git a/modules/gke-cluster-autopilot/versions.tf b/modules/gke-cluster-autopilot/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/gke-cluster-autopilot/versions.tf
+++ b/modules/gke-cluster-autopilot/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/gke-cluster-standard/README.md b/modules/gke-cluster-standard/README.md
index e80a4e6d..3c9b1eb8 100644
--- a/modules/gke-cluster-standard/README.md
+++ b/modules/gke-cluster-standard/README.md
@@ -1,10 +1,29 @@
-# GKE cluster Standard module
+# GKE Standard cluster module
-This module allows simplified creation and management of GKE Standard clusters and should be used together with the GKE nodepool module, as the default nodepool is turned off here and cannot be re-enabled. Some sensible defaults are set initially, in order to allow less verbose usage for most use cases.
+This module offers a way to create and manage Google Kubernetes Engine (GKE) [Standard clusters](https://cloud.google.com/kubernetes-engine/docs/concepts/choose-cluster-mode#why-standard). With its sensible default settings based on best practices and authors' experience as Google Cloud practitioners, the module accommodates for many common use cases out-of-the-box, without having to rely on verbose configuration.
+
+> [!IMPORTANT]
+> This module should be used together with the [`gke-nodepool`](../gke-nodepool/) module because the default node pool is deleted upon cluster creation and cannot be re-created.
+
+
+- [Example](#example)
+ - [GKE Standard cluster](#gke-standard-cluster)
+ - [Enable Dataplane V2](#enable-dataplane-v2)
+ - [Managing GKE logs](#managing-gke-logs)
+ - [Monitoring configuration](#monitoring-configuration)
+ - [Disable GKE logs or metrics collection](#disable-gke-logs-or-metrics-collection)
+ - [Cloud DNS](#cloud-dns)
+ - [Backup for GKE](#backup-for-gke)
+ - [Automatic creation of new secondary ranges](#automatic-creation-of-new-secondary-ranges)
+- [Variables](#variables)
+- [Outputs](#outputs)
+
## Example
-### GKE Cluster
+### GKE Standard cluster
+
+This example shows how to [create a zonal GKE cluster in Standard mode](https://cloud.google.com/kubernetes-engine/docs/how-to/creating-a-zonal-cluster).
```hcl
module "cluster-1" {
@@ -36,7 +55,9 @@ module "cluster-1" {
# tftest modules=1 resources=1 inventory=basic.yaml
```
-### GKE Cluster with Dataplane V2 enabled
+### Enable Dataplane V2
+
+This example shows how to [create a zonal GKE Cluster with Dataplane V2 enabled](https://cloud.google.com/kubernetes-engine/docs/how-to/dataplane-v2).
```hcl
module "cluster-1" {
@@ -45,12 +66,9 @@ module "cluster-1" {
name = "cluster-dataplane-v2"
location = "europe-west1-b"
vpc_config = {
- network = var.vpc.self_link
- subnetwork = var.subnet.self_link
- secondary_range_names = {
- pods = "pods"
- services = "services"
- }
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ secondary_range_names = {} # use default names "pods" and "services"
master_authorized_ranges = {
internal-vms = "10.0.0.0/8"
}
@@ -84,8 +102,9 @@ module "cluster-1" {
name = "cluster-1"
location = "europe-west1-b"
vpc_config = {
- network = var.vpc.self_link
- subnetwork = var.subnet.self_link
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ secondary_range_names = {}
}
logging_config = {
enable_workloads_logs = true
@@ -97,14 +116,9 @@ module "cluster-1" {
# tftest modules=1 resources=1 inventory=logging-config-enable-all.yaml
```
-### Disable GKE logs collection
+### Monitoring configuration
-This example shows how to fully disable logs collection on a GKE Standard cluster. This is not recommended.
-
-> **Warning**
-> If you've disabled Cloud Logging or Cloud Monitoring, GKE customer support
-> is offered on a best-effort basis and might require additional effort
-> from your engineering team.
+This example shows how to [configure collection of Kubernetes control plane metrics](https://cloud.google.com/stackdriver/docs/solutions/gke/managing-metrics#enable-control-plane-metrics). These metrics are optional and are not collected by default.
```hcl
module "cluster-1" {
@@ -113,8 +127,68 @@ module "cluster-1" {
name = "cluster-1"
location = "europe-west1-b"
vpc_config = {
- network = var.vpc.self_link
- subnetwork = var.subnet.self_link
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ secondary_range_names = {} # use default names "pods" and "services"
+ }
+ monitoring_config = {
+ enable_api_server_metrics = true
+ enable_controller_manager_metrics = true
+ enable_scheduler_metrics = true
+ }
+}
+# tftest modules=1 resources=1 inventory=monitoring-config-control-plane.yaml
+```
+
+The next example shows how to [configure collection of kube state metrics](https://cloud.google.com/stackdriver/docs/solutions/gke/managing-metrics#enable-ksm). These metrics are optional and are not collected by default.
+
+```hcl
+module "cluster-1" {
+ source = "./fabric/modules/gke-cluster-standard"
+ project_id = "myproject"
+ name = "cluster-1"
+ location = "europe-west1-b"
+ vpc_config = {
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ secondary_range_names = {} # use default names "pods" and "services"
+ }
+ monitoring_config = {
+ enable_daemonset_metrics = true
+ enable_deployment_metrics = true
+ enable_hpa_metrics = true
+ enable_pod_metrics = true
+ enable_statefulset_metrics = true
+ enable_storage_metrics = true
+ # Kube state metrics collection requires Google Cloud Managed Service for Prometheus,
+ # which is enabled by default.
+ # enable_managed_prometheus = true
+ }
+}
+# tftest modules=1 resources=1 inventory=monitoring-config-kube-state.yaml
+```
+
+The *control plane metrics* and *kube state metrics* collection can be configured in a single `monitoring_config` block.
+
+### Disable GKE logs or metrics collection
+
+> [!WARNING]
+> If you've disabled Cloud Logging or Cloud Monitoring, GKE customer support
+> is offered on a best-effort basis and might require additional effort
+> from your engineering team.
+
+This example shows how to fully disable logs collection on a zonal GKE Standard cluster. This is not recommended.
+
+```hcl
+module "cluster-1" {
+ source = "./fabric/modules/gke-cluster-standard"
+ project_id = "myproject"
+ name = "cluster-1"
+ location = "europe-west1-b"
+ vpc_config = {
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ secondary_range_names = {}
}
logging_config = {
enable_system_logs = false
@@ -123,6 +197,27 @@ module "cluster-1" {
# tftest modules=1 resources=1 inventory=logging-config-disable-all.yaml
```
+The next example shows how to fully disable metrics collection on a zonal GKE Standard cluster. This is not recommended.
+
+```hcl
+module "cluster-1" {
+ source = "./fabric/modules/gke-cluster-standard"
+ project_id = "myproject"
+ name = "cluster-1"
+ location = "europe-west1-b"
+ vpc_config = {
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ secondary_range_names = {}
+ }
+ monitoring_config = {
+ enable_system_metrics = false
+ enable_managed_prometheus = false
+ }
+}
+# tftest modules=1 resources=1 inventory=monitoring-config-disable-all.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.
@@ -136,7 +231,7 @@ module "cluster-1" {
vpc_config = {
network = var.vpc.self_link
subnetwork = var.subnet.self_link
- secondary_range_names = { pods = "pods", services = "services" }
+ secondary_range_names = {}
}
enable_features = {
dns = {
@@ -151,7 +246,15 @@ module "cluster-1" {
### Backup for GKE
-This example shows how to [enable the Backup for GKE agent and configure a Backup Plan](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/concepts/backup-for-gke) for GKE Standard clusters.
+> [!NOTE]
+> Although Backup for GKE can be enabled as an add-on when configuring your GKE clusters, it is a separate service from GKE.
+
+[Backup for GKE](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/concepts/backup-for-gke) is a service for backing up and restoring workloads in GKE clusters. It has two components:
+
+* A [Google Cloud API](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/reference/rest) that serves as the control plane for the service.
+* A GKE add-on (the [Backup for GKE agent](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/concepts/backup-for-gke#agent_overview)) that must be enabled in each cluster for which you wish to perform backup and restore operations.
+
+This example shows how to [enable Backup for GKE on a new zonal GKE Standard cluster](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/how-to/install#enable_on_a_new_cluster_optional) and [plan a set of backups](https://cloud.google.com/kubernetes-engine/docs/add-on/backup-for-gke/how-to/backup-plan).
```hcl
module "cluster-1" {
@@ -162,7 +265,7 @@ module "cluster-1" {
vpc_config = {
network = var.vpc.self_link
subnetwork = var.subnet.self_link
- secondary_range_names = { pods = "pods", services = "services" }
+ secondary_range_names = {}
}
backup_configs = {
enable_backup_agent = true
@@ -176,15 +279,37 @@ module "cluster-1" {
}
# tftest modules=1 resources=2 inventory=backup.yaml
```
+
+### Automatic creation of new secondary ranges
+
+You can use `var.vpc_config.secondary_range_blocks` to let GKE create new secondary ranges for the cluster. The example below reserves an available /14 block for pods and a /20 for services.
+
+```hcl
+module "cluster-1" {
+ source = "./fabric/modules/gke-cluster-standard"
+ 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_blocks = {
+ pods = ""
+ services = "/20" # can be an empty string as well
+ }
+ }
+}
+# tftest modules=1 resources=1
+```
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [location](variables.tf#L138) | Cluster zone or region. | string
| ✓ | |
-| [name](variables.tf#L210) | Cluster name. | string
| ✓ | |
-| [project_id](variables.tf#L236) | Cluster project id. | string
| ✓ | |
-| [vpc_config](variables.tf#L253) | VPC-level configuration. | object({…})
| ✓ | |
+| [name](variables.tf#L249) | Cluster name. | string
| ✓ | |
+| [project_id](variables.tf#L275) | Cluster project id. | string
| ✓ | |
+| [vpc_config](variables.tf#L292) | VPC-level configuration. | object({…})
| ✓ | |
| [backup_configs](variables.tf#L17) | Configuration for Backup for GKE. | object({…})
| | {}
|
| [cluster_autoscaling](variables.tf#L37) | Enable and configure limits for Node Auto-Provisioning with Cluster Autoscaler. | object({…})
| | null
|
| [description](variables.tf#L58) | Cluster description. | string
| | null
|
@@ -196,11 +321,11 @@ module "cluster-1" {
| [maintenance_config](variables.tf#L164) | Maintenance window configuration. | object({…})
| | {…}
|
| [max_pods_per_node](variables.tf#L187) | Maximum number of pods per node in this cluster. | number
| | 110
|
| [min_master_version](variables.tf#L193) | Minimum version of the master, defaults to the version of the most recent official release. | string
| | null
|
-| [monitoring_config](variables.tf#L199) | Monitoring components. | object({…})
| | {…}
|
-| [node_locations](variables.tf#L215) | Zones in which the cluster's nodes are located. | list(string)
| | []
|
-| [private_cluster_config](variables.tf#L222) | Private cluster configuration. | object({…})
| | null
|
-| [release_channel](variables.tf#L241) | Release channel for GKE upgrades. | string
| | null
|
-| [tags](variables.tf#L247) | Network tags applied to nodes. | list(string)
| | null
|
+| [monitoring_config](variables.tf#L199) | Monitoring configuration. Google Cloud Managed Service for Prometheus is enabled by default. | object({…})
| | {}
|
+| [node_locations](variables.tf#L254) | Zones in which the cluster's nodes are located. | list(string)
| | []
|
+| [private_cluster_config](variables.tf#L261) | Private cluster configuration. | object({…})
| | null
|
+| [release_channel](variables.tf#L280) | Release channel for GKE upgrades. | string
| | null
|
+| [tags](variables.tf#L286) | Network tags applied to nodes. | list(string)
| | null
|
## Outputs
diff --git a/modules/gke-cluster-standard/main.tf b/modules/gke-cluster-standard/main.tf
index 8f0df84f..d27f6ab3 100644
--- a/modules/gke-cluster-standard/main.tf
+++ b/modules/gke-cluster-standard/main.tf
@@ -40,8 +40,8 @@ resource "google_container_cluster" "cluster" {
: "DATAPATH_PROVIDER_UNSPECIFIED"
)
- # the default nodepool is deleted here, use the gke-nodepool module instead
- # default nodepool configuration based on a shielded_nodes variable
+ # the default node pool is deleted here, use the gke-nodepool module instead.
+ # the default node pool configuration is based on a shielded_nodes variable.
node_config {
dynamic "shielded_instance_config" {
for_each = var.enable_features.shielded_nodes ? [""] : []
@@ -164,12 +164,19 @@ resource "google_container_cluster" "cluster" {
}
}
+ dynamic "gateway_api_config" {
+ for_each = var.enable_features.gateway_api ? [""] : []
+ content {
+ channel = "CHANNEL_STANDARD"
+ }
+ }
+
dynamic "ip_allocation_policy" {
for_each = var.vpc_config.secondary_range_blocks != null ? [""] : []
content {
cluster_ipv4_cidr_block = var.vpc_config.secondary_range_blocks.pods
services_ipv4_cidr_block = var.vpc_config.secondary_range_blocks.services
- stack_type = try(var.vpc_config.stack_type, null)
+ stack_type = var.vpc_config.stack_type
}
}
dynamic "ip_allocation_policy" {
@@ -177,7 +184,7 @@ resource "google_container_cluster" "cluster" {
content {
cluster_secondary_range_name = var.vpc_config.secondary_range_names.pods
services_secondary_range_name = var.vpc_config.secondary_range_names.services
- stack_type = try(var.vpc_config.stack_type, null)
+ stack_type = var.vpc_config.stack_type
}
}
@@ -205,13 +212,6 @@ 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 = (
@@ -277,22 +277,28 @@ resource "google_container_cluster" "cluster" {
}
}
- dynamic "monitoring_config" {
- for_each = var.monitoring_config != null ? [""] : []
- content {
- enable_components = var.monitoring_config.enable_components
- dynamic "managed_prometheus" {
- for_each = (
- try(var.monitoring_config.managed_prometheus, null) == true ? [""] : []
- )
- content {
- enabled = true
- }
- }
+ monitoring_config {
+ enable_components = toset(compact([
+ # System metrics is the minimum requirement if any other metrics are enabled. This is checked by input var validation.
+ var.monitoring_config.enable_system_metrics ? "SYSTEM_COMPONENTS" : null,
+ # Control plane metrics
+ var.monitoring_config.enable_api_server_metrics ? "APISERVER" : null,
+ var.monitoring_config.enable_controller_manager_metrics ? "CONTROLLER_MANAGER" : null,
+ var.monitoring_config.enable_scheduler_metrics ? "SCHEDULER" : null,
+ # Kube state metrics
+ var.monitoring_config.enable_daemonset_metrics ? "DAEMONSET" : null,
+ var.monitoring_config.enable_deployment_metrics ? "DEPLOYMENT" : null,
+ var.monitoring_config.enable_hpa_metrics ? "HPA" : null,
+ var.monitoring_config.enable_pod_metrics ? "POD" : null,
+ var.monitoring_config.enable_statefulset_metrics ? "STATEFULSET" : null,
+ var.monitoring_config.enable_storage_metrics ? "STORAGE" : null,
+ ]))
+ managed_prometheus {
+ enabled = var.monitoring_config.enable_managed_prometheus
}
}
- # dataplane v2 has built-in network policies
+ # Dataplane V2 has built-in network policies
dynamic "network_policy" {
for_each = (
var.enable_addons.network_policy && !var.enable_features.dataplane_v2
diff --git a/modules/gke-cluster-standard/variables.tf b/modules/gke-cluster-standard/variables.tf
index b9c4a113..6b76efa7 100644
--- a/modules/gke-cluster-standard/variables.tf
+++ b/modules/gke-cluster-standard/variables.tf
@@ -197,13 +197,52 @@ variable "min_master_version" {
}
variable "monitoring_config" {
- description = "Monitoring components."
+ description = "Monitoring configuration. Google Cloud Managed Service for Prometheus is enabled by default."
type = object({
- enable_components = optional(list(string))
- managed_prometheus = optional(bool)
+ enable_system_metrics = optional(bool, true)
+
+ # Control plane metrics
+ enable_api_server_metrics = optional(bool, false)
+ enable_controller_manager_metrics = optional(bool, false)
+ enable_scheduler_metrics = optional(bool, false)
+
+ # Kube state metrics
+ enable_daemonset_metrics = optional(bool, false)
+ enable_deployment_metrics = optional(bool, false)
+ enable_hpa_metrics = optional(bool, false)
+ enable_pod_metrics = optional(bool, false)
+ enable_statefulset_metrics = optional(bool, false)
+ enable_storage_metrics = optional(bool, false)
+
+ # Google Cloud Managed Service for Prometheus
+ enable_managed_prometheus = optional(bool, true)
})
- default = {
- enable_components = ["SYSTEM_COMPONENTS"]
+ default = {}
+ nullable = false
+ validation {
+ condition = anytrue([
+ var.monitoring_config.enable_api_server_metrics,
+ var.monitoring_config.enable_controller_manager_metrics,
+ var.monitoring_config.enable_scheduler_metrics,
+ var.monitoring_config.enable_daemonset_metrics,
+ var.monitoring_config.enable_deployment_metrics,
+ var.monitoring_config.enable_hpa_metrics,
+ var.monitoring_config.enable_pod_metrics,
+ var.monitoring_config.enable_statefulset_metrics,
+ var.monitoring_config.enable_storage_metrics,
+ ]) ? var.monitoring_config.enable_system_metrics : true
+ error_message = "System metrics are the minimum required component for enabling metrics collection."
+ }
+ validation {
+ condition = anytrue([
+ var.monitoring_config.enable_daemonset_metrics,
+ var.monitoring_config.enable_deployment_metrics,
+ var.monitoring_config.enable_hpa_metrics,
+ var.monitoring_config.enable_pod_metrics,
+ var.monitoring_config.enable_statefulset_metrics,
+ var.monitoring_config.enable_storage_metrics,
+ ]) ? var.monitoring_config.enable_managed_prometheus : true
+ error_message = "Kube state metrics collection requires Google Cloud Managed Service for Prometheus to be enabled."
}
}
@@ -261,9 +300,9 @@ variable "vpc_config" {
services = string
}))
secondary_range_names = optional(object({
- pods = string
- services = string
- }), { pods = "pods", services = "services" })
+ pods = optional(string, "pods")
+ services = optional(string, "services")
+ }))
master_authorized_ranges = optional(map(string))
stack_type = optional(string)
})
diff --git a/modules/gke-cluster-standard/versions.tf b/modules/gke-cluster-standard/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/gke-cluster-standard/versions.tf
+++ b/modules/gke-cluster-standard/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/gke-hub/versions.tf b/modules/gke-hub/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/gke-hub/versions.tf
+++ b/modules/gke-hub/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/gke-nodepool/versions.tf b/modules/gke-nodepool/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/gke-nodepool/versions.tf
+++ b/modules/gke-nodepool/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/iam-service-account/README.md b/modules/iam-service-account/README.md
index 9fd6cba0..ea3362c7 100644
--- a/modules/iam-service-account/README.md
+++ b/modules/iam-service-account/README.md
@@ -45,23 +45,23 @@ module "myproject-default-service-accounts" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L113) | Name of the service account to create. | string
| ✓ | |
-| [project_id](variables.tf#L128) | Project id where service account will be created. | string
| ✓ | |
+| [name](variables.tf#L114) | Name of the service account to create. | string
| ✓ | |
+| [project_id](variables.tf#L129) | Project id where service account will be created. | string
| ✓ | |
| [description](variables.tf#L17) | Optional description. | string
| | null
|
| [display_name](variables.tf#L23) | Display name of the service account to create. | string
| | "Terraform-managed."
|
| [generate_key](variables.tf#L29) | Generate a key for service account. | bool
| | false
|
| [iam](variables.tf#L35) | IAM bindings on the service account in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
| [iam_billing_roles](variables.tf#L42) | Billing account roles granted to this service account, by billing account id. Non-authoritative. | map(list(string))
| | {}
|
-| [iam_bindings](variables.tf#L49) | Authoritative IAM bindings on the service account in {ROLE => {members = [], condition = {}}}. | map(object({…}))
| | {}
|
-| [iam_bindings_additive](variables.tf#L63) | Individual additive IAM bindings on the service account. Keys are arbitrary. | map(object({…}))
| | {}
|
-| [iam_folder_roles](variables.tf#L78) | Folder roles granted to this service account, by folder id. Non-authoritative. | map(list(string))
| | {}
|
-| [iam_organization_roles](variables.tf#L85) | Organization roles granted to this service account, by organization id. Non-authoritative. | map(list(string))
| | {}
|
-| [iam_project_roles](variables.tf#L92) | Project roles granted to this service account, by project id. | map(list(string))
| | {}
|
-| [iam_sa_roles](variables.tf#L99) | Service account roles granted to this service account, by service account name. | map(list(string))
| | {}
|
-| [iam_storage_roles](variables.tf#L106) | Storage roles granted to this service account, by bucket name. | map(list(string))
| | {}
|
-| [prefix](variables.tf#L118) | Prefix applied to service account names. | string
| | null
|
-| [public_keys_directory](variables.tf#L133) | Path to public keys data files to upload to the service account (should have `.pem` extension). | string
| | ""
|
-| [service_account_create](variables.tf#L139) | Create service account. When set to false, uses a data source to reference an existing service account. | bool
| | true
|
+| [iam_bindings](variables.tf#L49) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [iam_bindings_additive](variables.tf#L64) | Individual additive IAM bindings on the service account. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [iam_folder_roles](variables.tf#L79) | Folder roles granted to this service account, by folder id. Non-authoritative. | map(list(string))
| | {}
|
+| [iam_organization_roles](variables.tf#L86) | Organization roles granted to this service account, by organization id. Non-authoritative. | map(list(string))
| | {}
|
+| [iam_project_roles](variables.tf#L93) | Project roles granted to this service account, by project id. | map(list(string))
| | {}
|
+| [iam_sa_roles](variables.tf#L100) | Service account roles granted to this service account, by service account name. | map(list(string))
| | {}
|
+| [iam_storage_roles](variables.tf#L107) | Storage roles granted to this service account, by bucket name. | map(list(string))
| | {}
|
+| [prefix](variables.tf#L119) | Prefix applied to service account names. | string
| | null
|
+| [public_keys_directory](variables.tf#L134) | Path to public keys data files to upload to the service account (should have `.pem` extension). | string
| | ""
|
+| [service_account_create](variables.tf#L140) | Create service account. When set to false, uses a data source to reference an existing service account. | bool
| | true
|
## Outputs
diff --git a/modules/iam-service-account/iam.tf b/modules/iam-service-account/iam.tf
index a9423fb0..15ae1acc 100644
--- a/modules/iam-service-account/iam.tf
+++ b/modules/iam-service-account/iam.tf
@@ -71,7 +71,7 @@ resource "google_service_account_iam_binding" "authoritative" {
resource "google_service_account_iam_binding" "bindings" {
for_each = var.iam_bindings
service_account_id = local.service_account.name
- role = each.key
+ role = each.value.role
members = each.value.members
dynamic "condition" {
for_each = each.value.condition == null ? [] : [""]
diff --git a/modules/iam-service-account/variables.tf b/modules/iam-service-account/variables.tf
index c9ca7069..4a75af46 100644
--- a/modules/iam-service-account/variables.tf
+++ b/modules/iam-service-account/variables.tf
@@ -47,9 +47,10 @@ variable "iam_billing_roles" {
}
variable "iam_bindings" {
- description = "Authoritative IAM bindings on the service account in {ROLE => {members = [], condition = {}}}."
+ description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary."
type = map(object({
members = list(string)
+ role = string
condition = optional(object({
expression = string
title = string
diff --git a/modules/iam-service-account/versions.tf b/modules/iam-service-account/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/iam-service-account/versions.tf
+++ b/modules/iam-service-account/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/kms/README.md b/modules/kms/README.md
index 56acff46..ddbf4b5c 100644
--- a/modules/kms/README.md
+++ b/modules/kms/README.md
@@ -31,7 +31,7 @@ module "kms" {
}
keyring = { location = "europe-west1", name = "test" }
keyring_create = false
- keys = { key-a = null, key-b = null, key-c = null }
+ keys = { key-a = {}, key-b = {}, key-c = {} }
}
# tftest skip (uses data sources)
```
@@ -42,26 +42,34 @@ module "kms" {
module "kms" {
source = "./fabric/modules/kms"
project_id = "my-project"
- key_iam = {
- key-a = {
- "roles/cloudkms.admin" = ["user:user3@example.com"]
- }
+ keyring = {
+ location = "europe-west1"
+ name = "test"
}
- key_iam_bindings_additive = {
- key-b-am1 = {
- key = "key-b"
- member = "user:am1@example.com"
- role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
- }
- }
- keyring = { location = "europe-west1", name = "test" }
keys = {
- key-a = null
- key-b = { rotation_period = "604800s", labels = null }
- key-c = { rotation_period = null, labels = { env = "test" } }
+ key-a = {
+ iam = {
+ "roles/cloudkms.admin" = ["user:user3@example.com"]
+ }
+ }
+ key-b = {
+ rotation_period = "604800s"
+ iam_bindings_additive = {
+ key-b-iam1 = {
+ key = "key-b"
+ member = "user:am1@example.com"
+ role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
+ }
+ }
+ }
+ key-c = {
+ labels = {
+ env = "test"
+ }
+ }
}
}
-# tftest modules=1 resources=6
+# tftest modules=1 resources=6 inventory=basic.yaml
```
### Crypto key purpose
@@ -70,38 +78,35 @@ module "kms" {
module "kms" {
source = "./fabric/modules/kms"
project_id = "my-project"
- key_purpose = {
- key-c = {
+ keyring = {
+ location = "europe-west1"
+ name = "test"
+ }
+ keys = {
+ key-a = {
purpose = "ASYMMETRIC_SIGN"
version_template = {
algorithm = "EC_SIGN_P384_SHA384"
- protection_level = null
+ protection_level = "HSM"
}
}
}
- keyring = { location = "europe-west1", name = "test" }
- keys = { key-a = null, key-b = null, key-c = null }
}
-# tftest modules=1 resources=4
+# tftest modules=1 resources=2 inventory=purpose.yaml
```
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [keyring](variables.tf#L117) | Keyring attributes. | object({…})
| ✓ | |
-| [project_id](variables.tf#L140) | Project id where the keyring will be created. | string
| ✓ | |
+| [keyring](variables.tf#L54) | Keyring attributes. | object({…})
| ✓ | |
+| [project_id](variables.tf#L103) | Project id where the keyring will be created. | string
| ✓ | |
| [iam](variables.tf#L17) | Keyring IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
-| [iam_bindings](variables.tf#L23) | Keyring authoritative IAM bindings in {ROLE => {members = [], condition = {}}}. | map(object({…}))
| | {}
|
-| [iam_bindings_additive](variables.tf#L37) | Keyring individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
-| [key_iam](variables.tf#L52) | Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format. | map(map(list(string)))
| | {}
|
-| [key_iam_bindings](variables.tf#L58) | Key authoritative IAM bindings in {KEY => {ROLE => {members = [], condition = {}}}}. | map(object({…}))
| | {}
|
-| [key_iam_bindings_additive](variables.tf#L72) | Key individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
-| [key_purpose](variables.tf#L88) | Per-key purpose, if not set defaults will be used. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | map(object({…}))
| | {}
|
-| [key_purpose_defaults](variables.tf#L100) | Defaults used for key purpose when not defined at the key level. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | object({…})
| | {…}
|
-| [keyring_create](variables.tf#L125) | Set to false to manage keys and IAM bindings in an existing keyring. | bool
| | true
|
-| [keys](variables.tf#L131) | Key names and base attributes. Set attributes to null if not needed. | map(object({…}))
| | {}
|
-| [tag_bindings](variables.tf#L145) | Tag bindings for this keyring, in key => tag value id format. | map(string)
| | null
|
+| [iam_bindings](variables.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [iam_bindings_additive](variables.tf#L39) | Keyring individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [keyring_create](variables.tf#L62) | Set to false to manage keys and IAM bindings in an existing keyring. | bool
| | true
|
+| [keys](variables.tf#L68) | Key names and base attributes. Set attributes to null if not needed. | map(object({…}))
| | {}
|
+| [tag_bindings](variables.tf#L108) | Tag bindings for this keyring, in key => tag value id format. | map(string)
| | {}
|
## Outputs
diff --git a/modules/kms/iam.tf b/modules/kms/iam.tf
index 9a78c2cf..8ac17a56 100644
--- a/modules/kms/iam.tf
+++ b/modules/kms/iam.tf
@@ -16,24 +16,36 @@
locals {
key_iam = flatten([
- for key, roles in var.key_iam : [
- for role, members in roles : {
- key = key
+ for k, v in var.keys : [
+ for role, members in v.iam : {
+ key = k
role = role
members = members
}
]
])
- key_iam_bindings = flatten([
- for key, roles in var.key_iam_bindings : [
- for role, data in roles : {
- key = key
- role = role
+ key_iam_bindings = merge([
+ for k, v in var.keys : {
+ for binding_key, data in v.iam_bindings :
+ binding_key => {
+ key = k
+ role = data.role
members = data.members
condition = data.condition
}
- ]
- ])
+ }
+ ]...)
+ key_iam_bindings_additive = merge([
+ for k, v in var.keys : {
+ for binding_key, data in v.iam_bindings_additive :
+ binding_key => {
+ key = k
+ role = data.role
+ member = data.member
+ condition = data.condition
+ }
+ }
+ ]...)
}
resource "google_kms_key_ring_iam_binding" "authoritative" {
@@ -46,7 +58,7 @@ resource "google_kms_key_ring_iam_binding" "authoritative" {
resource "google_kms_key_ring_iam_binding" "bindings" {
for_each = var.iam_bindings
key_ring_id = local.keyring.id
- role = each.key
+ role = each.value.role
members = each.value.members
dynamic "condition" {
for_each = each.value.condition == null ? [] : [""]
@@ -84,10 +96,7 @@ resource "google_kms_crypto_key_iam_binding" "authoritative" {
}
resource "google_kms_crypto_key_iam_binding" "bindings" {
- for_each = {
- for binding in local.key_iam_bindings :
- "${binding.key}.${binding.role}" => binding
- }
+ for_each = local.key_iam_bindings
role = each.value.role
crypto_key_id = google_kms_crypto_key.default[each.value.key].id
members = each.value.members
@@ -102,7 +111,7 @@ resource "google_kms_crypto_key_iam_binding" "bindings" {
}
resource "google_kms_crypto_key_iam_member" "members" {
- for_each = var.key_iam_bindings_additive
+ for_each = local.key_iam_bindings_additive
crypto_key_id = google_kms_crypto_key.default[each.value.key].id
role = each.value.role
member = each.value.member
diff --git a/modules/kms/main.tf b/modules/kms/main.tf
index 26624f15..6be7c812 100644
--- a/modules/kms/main.tf
+++ b/modules/kms/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,11 +15,6 @@
*/
locals {
- key_purpose = {
- for key, attrs in var.keys : key => try(
- var.key_purpose[key], var.key_purpose_defaults
- )
- }
keyring = (
var.keyring_create
? google_kms_key_ring.default.0
@@ -42,17 +37,19 @@ resource "google_kms_key_ring" "default" {
}
resource "google_kms_crypto_key" "default" {
- for_each = var.keys
- key_ring = local.keyring.id
- name = each.key
- rotation_period = try(each.value.rotation_period, null)
- labels = try(each.value.labels, null)
- purpose = try(local.key_purpose[each.key].purpose, null)
+ for_each = var.keys
+ key_ring = local.keyring.id
+ name = each.key
+ rotation_period = each.value.rotation_period
+ labels = each.value.labels
+ purpose = each.value.purpose
+ skip_initial_version_creation = each.value.skip_initial_version_creation
+
dynamic "version_template" {
- for_each = local.key_purpose[each.key].version_template == null ? [] : [""]
+ for_each = each.value.version_template == null ? [] : [""]
content {
- algorithm = local.key_purpose[each.key].version_template.algorithm
- protection_level = local.key_purpose[each.key].version_template.protection_level
+ algorithm = each.value.version_template.algorithm
+ protection_level = each.value.version_template.protection_level
}
}
}
diff --git a/modules/kms/tags.tf b/modules/kms/tags.tf
index 894c28aa..c0955c62 100644
--- a/modules/kms/tags.tf
+++ b/modules/kms/tags.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,7 +15,7 @@
*/
resource "google_tags_tag_binding" "binding" {
- for_each = coalesce(var.tag_bindings, {})
+ for_each = var.tag_bindings
parent = "//cloudresourcemanager.googleapis.com/${local.keyring.id}"
tag_value = each.value
}
diff --git a/modules/kms/variables.tf b/modules/kms/variables.tf
index 44c98036..30861764 100644
--- a/modules/kms/variables.tf
+++ b/modules/kms/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.
@@ -18,12 +18,14 @@ variable "iam" {
description = "Keyring IAM bindings in {ROLE => [MEMBERS]} format."
type = map(list(string))
default = {}
+ nullable = false
}
variable "iam_bindings" {
- description = "Keyring authoritative IAM bindings in {ROLE => {members = [], condition = {}}}."
+ description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary."
type = map(object({
members = list(string)
+ role = string
condition = optional(object({
expression = string
title = string
@@ -49,71 +51,6 @@ variable "iam_bindings_additive" {
default = {}
}
-variable "key_iam" {
- description = "Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format."
- type = map(map(list(string)))
- default = {}
-}
-
-variable "key_iam_bindings" {
- description = "Key authoritative IAM bindings in {KEY => {ROLE => {members = [], condition = {}}}}."
- type = map(object({
- members = list(string)
- condition = optional(object({
- expression = string
- title = string
- description = optional(string)
- }))
- }))
- nullable = false
- default = {}
-}
-
-variable "key_iam_bindings_additive" {
- description = "Key individual additive IAM bindings. Keys are arbitrary."
- type = map(object({
- key = string
- member = string
- role = string
- condition = optional(object({
- expression = string
- title = string
- description = optional(string)
- }))
- }))
- nullable = false
- default = {}
-}
-
-variable "key_purpose" {
- description = "Per-key purpose, if not set defaults will be used. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required."
- type = map(object({
- purpose = string
- version_template = object({
- algorithm = string
- protection_level = string
- })
- }))
- default = {}
-}
-
-variable "key_purpose_defaults" {
- description = "Defaults used for key purpose when not defined at the key level. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required."
- type = object({
- purpose = string
- version_template = object({
- algorithm = string
- protection_level = string
- })
- })
- default = {
- purpose = null
- version_template = null
- }
-}
-
-# cf https://cloud.google.com/kms/docs/locations
-
variable "keyring" {
description = "Keyring attributes."
type = object({
@@ -131,10 +68,36 @@ variable "keyring_create" {
variable "keys" {
description = "Key names and base attributes. Set attributes to null if not needed."
type = map(object({
- rotation_period = string
- labels = map(string)
+ rotation_period = optional(string)
+ labels = optional(map(string))
+ purpose = optional(string, "ENCRYPT_DECRYPT")
+ skip_initial_version_creation = optional(bool, false)
+ version_template = optional(object({
+ algorithm = string
+ protection_level = optional(string, "SOFTWARE")
+ }))
+
+ iam = optional(map(list(string)), {})
+ iam_bindings = optional(map(object({
+ members = list(string)
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
+ iam_bindings_additive = optional(map(object({
+ member = string
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
}))
- default = {}
+ default = {}
+ nullable = false
}
variable "project_id" {
@@ -145,5 +108,6 @@ variable "project_id" {
variable "tag_bindings" {
description = "Tag bindings for this keyring, in key => tag value id format."
type = map(string)
- default = null
+ default = {}
+ nullable = false
}
diff --git a/modules/kms/versions.tf b/modules/kms/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/kms/versions.tf
+++ b/modules/kms/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/logging-bucket/versions.tf b/modules/logging-bucket/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/logging-bucket/versions.tf
+++ b/modules/logging-bucket/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/ncc-spoke-ra/versions.tf b/modules/ncc-spoke-ra/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/ncc-spoke-ra/versions.tf
+++ b/modules/ncc-spoke-ra/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-address/versions.tf b/modules/net-address/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-address/versions.tf
+++ b/modules/net-address/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-cloudnat/versions.tf b/modules/net-cloudnat/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-cloudnat/versions.tf
+++ b/modules/net-cloudnat/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-firewall-policy/versions.tf b/modules/net-firewall-policy/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-firewall-policy/versions.tf
+++ b/modules/net-firewall-policy/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-ipsec-over-interconnect/versions.tf b/modules/net-ipsec-over-interconnect/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-ipsec-over-interconnect/versions.tf
+++ b/modules/net-ipsec-over-interconnect/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-lb-app-ext/versions.tf b/modules/net-lb-app-ext/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-lb-app-ext/versions.tf
+++ b/modules/net-lb-app-ext/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-lb-app-int/versions.tf b/modules/net-lb-app-int/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-lb-app-int/versions.tf
+++ b/modules/net-lb-app-int/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-lb-ext/versions.tf b/modules/net-lb-ext/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-lb-ext/versions.tf
+++ b/modules/net-lb-ext/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-lb-int/versions.tf b/modules/net-lb-int/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-lb-int/versions.tf
+++ b/modules/net-lb-int/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-lb-proxy-int/versions.tf b/modules/net-lb-proxy-int/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-lb-proxy-int/versions.tf
+++ b/modules/net-lb-proxy-int/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-swp/versions.tf b/modules/net-swp/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-swp/versions.tf
+++ b/modules/net-swp/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-vlan-attachment/README.md b/modules/net-vlan-attachment/README.md
index b013fe08..a1711709 100644
--- a/modules/net-vlan-attachment/README.md
+++ b/modules/net-vlan-attachment/README.md
@@ -81,7 +81,7 @@ module "example-va" {
name = google_compute_router.interconnect-router.name
}
}
-# tftest modules=1 resources=3
+# tftest modules=1 resources=2
```
### Dedicated Interconnect - Two VLAN Attachments on a single region (99.9% SLA)
@@ -201,7 +201,7 @@ module "example-va-b" {
edge_availability_domain = "AVAILABILITY_DOMAIN_2"
}
}
-# tftest modules=2 resources=5
+# tftest modules=2 resources=3
```
### Dedicated Interconnect - Four VLAN Attachments on two regions (99.99% SLA)
@@ -431,10 +431,10 @@ module "example-va-b-ew12" {
edge_availability_domain = "AVAILABILITY_DOMAIN_2"
}
}
-# tftest modules=4 resources=10
+# tftest modules=4 resources=6
```
-### IPSec over Interconnect enabled setup
+### IPSec for Dedicated Interconnect
Refer to the [HA VPN over Interconnect Blueprint](../../blueprints/networking/ha-vpn-over-interconnect/) for an all-encompassing example.
@@ -494,6 +494,47 @@ module "example-va-b" {
}
# tftest modules=2 resources=9
```
+
+### IPSec for Partner Interconnect
+
+```hcl
+module "example-va-a" {
+ source = "./fabric/modules/net-vlan-attachment"
+ project_id = "myproject"
+ network = "mynet"
+ region = "europe-west8"
+ name = "encrypted-vlan-attachment-a"
+ description = "example-va-a vlan attachment"
+ peer_asn = "65001"
+ router_config = {
+ create = true
+ }
+ partner_interconnect_config = {
+ edge_availability_domain = "AVAILABILITY_DOMAIN_1"
+ }
+ vpn_gateways_ip_range = "10.255.255.0/29" # Allows for up to 8 tunnels
+}
+
+module "example-va-b" {
+ source = "./fabric/modules/net-vlan-attachment"
+ project_id = "myproject"
+ network = "mynet"
+ region = "europe-west8"
+ name = "encrypted-vlan-attachment-b"
+ description = "example-va-b vlan attachment"
+ peer_asn = "65001"
+ router_config = {
+ create = true
+ }
+ partner_interconnect_config = {
+ edge_availability_domain = "AVAILABILITY_DOMAIN_2"
+ }
+ vpn_gateways_ip_range = "10.255.255.8/29" # Allows for up to 8 tunnels
+}
+# tftest modules=2 resources=6
+```
+
+
## Variables
diff --git a/modules/net-vlan-attachment/main.tf b/modules/net-vlan-attachment/main.tf
index 877ec4a7..5cf5c328 100644
--- a/modules/net-vlan-attachment/main.tf
+++ b/modules/net-vlan-attachment/main.tf
@@ -61,7 +61,15 @@ resource "google_compute_router" "encrypted" {
region = var.region
encrypted_interconnect_router = true
bgp {
- asn = var.router_config.asn
+ asn = var.router_config.asn
+ advertise_mode = var.dedicated_interconnect_config == null ? "DEFAULT" : "CUSTOM"
+ dynamic "advertised_ip_ranges" {
+ for_each = var.dedicated_interconnect_config == null ? var.ipsec_gateway_ip_ranges : {}
+ content {
+ description = advertised_ip_ranges.key
+ range = advertised_ip_ranges.value
+ }
+ }
}
}
@@ -106,13 +114,14 @@ resource "google_compute_router_interface" "default" {
}
resource "google_compute_router_peer" "default" {
+ count = var.dedicated_interconnect_config != null ? 1 : 0
name = "${var.name}-peer"
project = var.project_id
router = local.router
region = var.region
peer_ip_address = split("/", google_compute_interconnect_attachment.default.customer_router_ip_address)[0]
peer_asn = var.peer_asn
- interface = "${var.name}-intf"
+ interface = google_compute_router_interface.default[0].name
advertised_route_priority = 100
advertise_mode = "CUSTOM"
diff --git a/modules/net-vlan-attachment/versions.tf b/modules/net-vlan-attachment/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-vlan-attachment/versions.tf
+++ b/modules/net-vlan-attachment/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-vpc-firewall/versions.tf b/modules/net-vpc-firewall/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-vpc-firewall/versions.tf
+++ b/modules/net-vpc-firewall/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-vpc-peering/versions.tf b/modules/net-vpc-peering/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-vpc-peering/versions.tf
+++ b/modules/net-vpc-peering/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md
index 3aaaa2a7..ea86930e 100644
--- a/modules/net-vpc/README.md
+++ b/modules/net-vpc/README.md
@@ -112,38 +112,35 @@ module "vpc" {
name = "subnet-1"
region = "europe-west1"
ip_cidr_range = "10.0.1.0/24"
+ iam = {
+ "roles/compute.networkUser" = [
+ "user:user1@example.com", "group:group1@example.com"
+ ]
+ }
+ iam_bindings = {
+ subnet-1-iam = {
+ members = ["group:group2@example.com"]
+ role = "roles/compute.networkUser"
+ condition = {
+ expression = "resource.matchTag('123456789012/env', 'prod')"
+ title = "test_condition"
+ }
+ }
+ }
},
{
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"
- ]
- }
- }
- subnet_iam_bindings = {
- "europe-west1/subnet-1" = {
- "roles/compute.networkUser" = {
- members = ["group:group2@example.com"]
- condition = {
- expression = "resource.matchTag('123456789012/env', 'prod')"
- title = "test_condition"
+ iam_bindings_additive = {
+ subnet-2-iam = {
+ member = "user:am1@example.com"
+ role = "roles/compute.networkUser"
+ subnet = "europe-west1/subnet-2"
}
}
}
- }
- subnet_iam_bindings_additive = {
- subnet-2-am1 = {
- member = "user:am1@example.com"
- role = "roles/compute.networkUser"
- subnet = "europe-west1/subnet-2"
- }
- }
+ ]
}
# tftest modules=1 resources=8 inventory=subnet-iam.yaml
```
@@ -212,6 +209,15 @@ module "vpc-host" {
pods = "172.16.0.0/20"
services = "192.168.0.0/24"
}
+ iam = {
+ "roles/compute.networkUser" = [
+ local.service_project_1.cloud_services_service_account,
+ local.service_project_1.gke_service_account
+ ]
+ "roles/compute.securityAdmin" = [
+ local.service_project_1.gke_service_account
+ ]
+ }
}
]
shared_vpc_host = true
@@ -219,17 +225,6 @@ module "vpc-host" {
local.service_project_1.project_id,
local.service_project_2.project_id
]
- subnet_iam = {
- "europe-west1/subnet-1" = {
- "roles/compute.networkUser" = [
- local.service_project_1.cloud_services_service_account,
- local.service_project_1.gke_service_account
- ]
- "roles/compute.securityAdmin" = [
- local.service_project_1.gke_service_account
- ]
- }
- }
}
# tftest modules=1 resources=9 inventory=shared-vpc.yaml
```
@@ -299,6 +294,13 @@ module "vpc" {
name = "regional-proxy"
region = "europe-west1"
active = true
+ },
+ {
+ ip_cidr_range = "10.0.4.0/24"
+ name = "global-proxy"
+ region = "australia-southeast2"
+ active = true
+ global = true
}
]
subnets_psc = [
@@ -309,7 +311,7 @@ module "vpc" {
}
]
}
-# tftest modules=1 resources=5 inventory=proxy-only-subnets.yaml
+# tftest modules=1 resources=6 inventory=proxy-only-subnets.yaml
```
### DNS Policies
@@ -343,12 +345,14 @@ The `net-vpc` module includes a subnet factory (see [Resource Factories](../../b
```hcl
module "vpc" {
- source = "./fabric/modules/net-vpc"
- project_id = "my-project"
- name = "my-network"
- data_folder = "config/subnets"
+ source = "./fabric/modules/net-vpc"
+ project_id = "my-project"
+ name = "my-network"
+ factories_config = {
+ subnets_folder = "config/subnets"
+ }
}
-# tftest modules=1 resources=9 files=subnet-simple,subnet-simple-2,subnet-detailed,subnet-proxy,subnet-psc inventory=factory.yaml
+# tftest modules=1 resources=10 files=subnet-simple,subnet-simple-2,subnet-detailed,subnet-proxy,subnet-proxy-global,subnet-psc inventory=factory.yaml
```
```yaml
@@ -372,31 +376,39 @@ description: Sample description
ip_cidr_range: 10.0.0.0/24
# optional attributes
enable_private_access: false # defaults to true
-iam: # grant roles/compute.networkUser
- - group:lorem@example.com
- - serviceAccount:fbz@prj.iam.gserviceaccount.com
- - user:foobar@example.com
+iam:
+ roles/compute.networkUser:
+ - group:lorem@example.com
+ - serviceAccount:fbz@prj.iam.gserviceaccount.com
+ - user:foobar@example.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
+flow_logs_config: # enable, set to empty map to use defaults
aggregation_interval: "INTERVAL_5_SEC"
flow_sampling: 0.5
metadata: "INCLUDE_ALL_METADATA"
- filter_expression: null
```
```yaml
# tftest-file id=subnet-proxy path=config/subnets/subnet-proxy.yaml
region: europe-west4
ip_cidr_range: 10.1.0.0/24
-purpose: REGIONAL_MANAGED_PROXY
+proxy_only: true
+```
+
+```yaml
+# tftest-file id=subnet-proxy-global path=config/subnets/subnet-proxy-global.yaml
+region: australia-southeast2
+ip_cidr_range: 10.4.0.0/24
+proxy_only: true
+global: true
```
```yaml
# tftest-file id=subnet-psc path=config/subnets/subnet-psc.yaml
region: europe-west4
ip_cidr_range: 10.2.0.0/24
-purpose: PRIVATE_SERVICE_CONNECT
+psc: true
```
### Custom Routes
@@ -525,30 +537,27 @@ module "vpc" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L93) | The name of the network being created. | string
| ✓ | |
-| [project_id](variables.tf#L109) | The ID of the project where this VPC will be created. | string
| ✓ | |
+| [name](variables.tf#L95) | The name of the network being created. | string
| ✓ | |
+| [project_id](variables.tf#L111) | The ID of the project where this VPC will be created. | string
| ✓ | |
| [auto_create_subnetworks](variables.tf#L17) | Set to true to create an auto mode subnet, defaults to custom mode. | bool
| | false
|
| [create_googleapis_routes](variables.tf#L23) | Toggle creation of googleapis private/restricted routes. Disabled when vpc creation is turned off, or when set to null. | object({…})
| | {}
|
-| [data_folder](variables.tf#L34) | An optional folder containing the subnet configurations in YaML format. | string
| | null
|
-| [delete_default_routes_on_create](variables.tf#L40) | Set to true to delete the default routes at creation time. | bool
| | false
|
-| [description](variables.tf#L46) | An optional description of this resource (triggers recreation on change). | string
| | "Terraform-managed."
|
-| [dns_policy](variables.tf#L52) | DNS policy setup for the VPC. | object({…})
| | null
|
-| [firewall_policy_enforcement_order](variables.tf#L65) | Order that Firewall Rules and Firewall Policies are evaluated. Can be either 'BEFORE_CLASSIC_FIREWALL' or 'AFTER_CLASSIC_FIREWALL'. | string
| | "AFTER_CLASSIC_FIREWALL"
|
-| [ipv6_config](variables.tf#L77) | Optional IPv6 configuration for this network. | object({…})
| | {}
|
-| [mtu](variables.tf#L87) | Maximum Transmission Unit in bytes. The minimum value for this field is 1460 (the default) and the maximum value is 1500 bytes. | number
| | null
|
-| [peering_config](variables.tf#L98) | VPC peering configuration. | object({…})
| | null
|
-| [psa_config](variables.tf#L114) | The Private Service Access configuration for Service Networking. | object({…})
| | null
|
-| [routes](variables.tf#L124) | Network routes, keyed by name. | map(object({…}))
| | {}
|
-| [routing_mode](variables.tf#L145) | The network routing mode (default 'GLOBAL'). | string
| | "GLOBAL"
|
-| [shared_vpc_host](variables.tf#L155) | Enable shared VPC for this project. | bool
| | false
|
-| [shared_vpc_service_projects](variables.tf#L161) | Shared VPC service projects to register with this host. | list(string)
| | []
|
-| [subnet_iam](variables.tf#L167) | Subnet IAM bindings in {REGION/NAME => {ROLE => [MEMBERS]} format. | map(map(list(string)))
| | {}
|
-| [subnet_iam_bindings](variables.tf#L173) | Authoritative IAM bindings in {REGION/NAME => {ROLE => {members = [], condition = {}}}}. | map(map(object({…})))
| | {}
|
-| [subnet_iam_bindings_additive](variables.tf#L187) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
-| [subnets](variables.tf#L203) | Subnet configuration. | list(object({…}))
| | []
|
-| [subnets_proxy_only](variables.tf#L229) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…}))
| | []
|
-| [subnets_psc](variables.tf#L241) | List of subnets for Private Service Connect service producers. | list(object({…}))
| | []
|
-| [vpc_create](variables.tf#L252) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool
| | true
|
+| [delete_default_routes_on_create](variables.tf#L34) | Set to true to delete the default routes at creation time. | bool
| | false
|
+| [description](variables.tf#L40) | An optional description of this resource (triggers recreation on change). | string
| | "Terraform-managed."
|
+| [dns_policy](variables.tf#L46) | DNS policy setup for the VPC. | object({…})
| | null
|
+| [factories_config](variables.tf#L59) | Paths to data files and folders that enable factory functionality. | object({…})
| | null
|
+| [firewall_policy_enforcement_order](variables.tf#L67) | Order that Firewall Rules and Firewall Policies are evaluated. Can be either 'BEFORE_CLASSIC_FIREWALL' or 'AFTER_CLASSIC_FIREWALL'. | string
| | "AFTER_CLASSIC_FIREWALL"
|
+| [ipv6_config](variables.tf#L79) | Optional IPv6 configuration for this network. | object({…})
| | {}
|
+| [mtu](variables.tf#L89) | Maximum Transmission Unit in bytes. The minimum value for this field is 1460 (the default) and the maximum value is 1500 bytes. | number
| | null
|
+| [peering_config](variables.tf#L100) | VPC peering configuration. | object({…})
| | null
|
+| [psa_config](variables.tf#L116) | The Private Service Access configuration for Service Networking. | object({…})
| | null
|
+| [routes](variables.tf#L126) | Network routes, keyed by name. | map(object({…}))
| | {}
|
+| [routing_mode](variables.tf#L147) | The network routing mode (default 'GLOBAL'). | string
| | "GLOBAL"
|
+| [shared_vpc_host](variables.tf#L157) | Enable shared VPC for this project. | bool
| | false
|
+| [shared_vpc_service_projects](variables.tf#L163) | Shared VPC service projects to register with this host. | list(string)
| | []
|
+| [subnets](variables.tf#L169) | Subnet configuration. | list(object({…}))
| | []
|
+| [subnets_proxy_only](variables.tf#L216) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…}))
| | []
|
+| [subnets_psc](variables.tf#L250) | List of subnets for Private Service Connect service producers. | list(object({…}))
| | []
|
+| [vpc_create](variables.tf#L282) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool
| | true
|
## Outputs
diff --git a/modules/net-vpc/outputs.tf b/modules/net-vpc/outputs.tf
index fbf07dba..503923d9 100644
--- a/modules/net-vpc/outputs.tf
+++ b/modules/net-vpc/outputs.tf
@@ -136,4 +136,4 @@ output "subnets_proxy_only" {
output "subnets_psc" {
description = "Private Service Connect subnet resources."
value = { for k, v in google_compute_subnetwork.psc : k => v }
-}
+}
\ No newline at end of file
diff --git a/modules/net-vpc/subnets.tf b/modules/net-vpc/subnets.tf
index 0e656fd8..fe5abea9 100644
--- a/modules/net-vpc/subnets.tf
+++ b/modules/net-vpc/subnets.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.
@@ -18,66 +18,114 @@
locals {
_factory_data = {
- for f in try(fileset(var.data_folder, "**/*.yaml"), []) :
- trimsuffix(basename(f), ".yaml") => yamldecode(file("${var.data_folder}/${f}"))
+ for f in try(fileset(var.factories_config.subnets_folder, "**/*.yaml"), []) :
+ trimsuffix(basename(f), ".yaml") => yamldecode(file("${var.factories_config.subnets_folder}/${f}"))
}
_factory_subnets = {
- for k, v in local._factory_data : "${v.region}/${try(v.name, k)}" => {
- name = try(v.name, k)
- ip_cidr_range = v.ip_cidr_range
- region = v.region
+ for k, v in local._factory_data :
+ "${v.region}/${try(v.name, k)}" => {
+ active = try(v.active, true)
description = try(v.description, null)
enable_private_access = try(v.enable_private_access, true)
- flow_logs_config = try(v.flow_logs, null)
- ipv6 = try(v.ipv6, null)
- secondary_ip_ranges = try(v.secondary_ip_ranges, null)
- iam = try(v.iam, [])
- iam_members = try(v.iam_members, [])
- purpose = try(v.purpose, null)
- active = try(v.active, null)
+ flow_logs_config = can(v.flow_logs_config) ? {
+ aggregation_interval = try(v.flow_logs_config.aggregation_interval, null)
+ filter_expression = try(v.flow_logs_config.filter_expression, null)
+ flow_sampling = try(v.flow_logs_config.flow_sampling, null)
+ metadata = try(v.flow_logs_config.metadata, null)
+ metadata_fields = try(v.flow_logs_config.metadata_fields, null)
+ } : null
+ global = try(v.global, false)
+ ip_cidr_range = v.ip_cidr_range
+ ipv6 = !can(v.ipv6) ? null : {
+ access_type = try(v.ipv6.access_type, "INTERNAL")
+ }
+ name = try(v.name, k)
+ region = v.region
+ secondary_ip_ranges = try(v.secondary_ip_ranges, null)
+ iam = try(v.iam, {})
+ iam_bindings = !can(v.iam_bindings) ? {} : {
+ for k2, v2 in v.iam_bindings :
+ k2 => {
+ role = v2.role
+ members = v2.members
+ condition = !can(v2.condition) ? null : {
+ expression = v2.condition.expression
+ title = v2.condition.title
+ description = try(v2.condition.description, null)
+ }
+ }
+ }
+ iam_bindings_additive = !can(v.iam_bindings_additive) ? {} : {
+ for k2, v2 in v.iam_bindings_additive :
+ k2 => {
+ member = v2.member
+ role = v2.role
+ condition = !can(v2.condition) ? null : {
+ expression = v2.condition.expression
+ title = v2.condition.title
+ description = try(v2.condition.description, null)
+ }
+ }
+ }
+ _is_regular = !try(v.psc == true, false) && !try(v.proxy_only == true, false)
+ _is_psc = try(v.psc == true, false)
+ _is_proxy_only = try(v.proxy_only == true, false)
}
}
- _factory_subnets_iam = [
- for k, v in local._factory_subnets : {
- subnet = k
- role = "roles/compute.networkUser"
- members = v.iam
- } if v.purpose == null && v.iam != null
- ]
- _subnet_iam = flatten([
- for subnet, roles in(var.subnet_iam == null ? {} : var.subnet_iam) : [
- for role, members in roles : {
- members = members
- role = role
- subnet = subnet
- }
- ]
- ])
- subnet_iam = concat(
- [for k in local._factory_subnets_iam : k if length(k.members) > 0],
- local._subnet_iam
+
+ all_subnets = merge(
+ { for k, v in google_compute_subnetwork.subnetwork : k => v },
+ { for k, v in google_compute_subnetwork.proxy_only : k => v },
+ { for k, v in google_compute_subnetwork.psc : k => v }
)
- subnet_iam_bindings = flatten([
- for subnet, roles in(var.subnet_iam_bindings == null ? {} : var.subnet_iam_bindings) : [
- for role, data in roles : {
- role = role
- subnet = subnet
+ subnet_iam = flatten(concat(
+ [
+ for s in concat(var.subnets, var.subnets_psc, var.subnets_proxy_only, values(local._factory_subnets)) : [
+ for role, members in s.iam :
+ {
+ role = role
+ members = members
+ subnet = "${s.region}/${s.name}"
+ }
+ ]
+ ],
+ ))
+ subnet_iam_bindings = merge([
+ for s in concat(var.subnets, var.subnets_psc, var.subnets_proxy_only, values(local._factory_subnets)) : {
+ for key, data in s.iam_bindings :
+ key => {
+ role = data.role
+ subnet = "${s.region}/${s.name}"
members = data.members
condition = data.condition
}
- ]
- ])
+ }
+ ]...)
+ # note: all additive bindings share a single namespace for the key.
+ # In other words, if you have multiple additive bindings with the
+ # same name, only one will be used
+ subnet_iam_bindings_additive = merge([
+ for s in concat(var.subnets, var.subnets_psc, var.subnets_proxy_only, values(local._factory_subnets)) : {
+ for key, data in s.iam_bindings_additive :
+ key => {
+ role = data.role
+ subnet = "${s.region}/${s.name}"
+ member = data.member
+ condition = data.condition
+ }
+ }
+ ]...)
subnets = merge(
{ for s in var.subnets : "${s.region}/${s.name}" => s },
- { for k, v in local._factory_subnets : k => v if v.purpose == null }
+ { for k, v in local._factory_subnets : k => v if v._is_regular }
)
subnets_proxy_only = merge(
{ for s in var.subnets_proxy_only : "${s.region}/${s.name}" => s },
- { for k, v in local._factory_subnets : k => v if v.purpose == "REGIONAL_MANAGED_PROXY" }
+ { for k, v in local._factory_subnets : k => v if v._is_proxy_only },
)
subnets_psc = merge(
{ for s in var.subnets_psc : "${s.region}/${s.name}" => s },
- { for k, v in local._factory_subnets : k => v if v.purpose == "PRIVATE_SERVICE_CONNECT" }
+ { for k, v in local._factory_subnets : k => v if v._is_psc }
)
}
@@ -128,13 +176,12 @@ resource "google_compute_subnetwork" "proxy_only" {
name = each.value.name
region = each.value.region
ip_cidr_range = each.value.ip_cidr_range
- description = (
- each.value.description == null
- ? "Terraform-managed proxy-only subnet for Regional HTTPS or Internal HTTPS LB."
- : each.value.description
+ description = coalesce(
+ each.value.description,
+ "Terraform-managed proxy-only subnet for Regional HTTPS, Internal HTTPS or Cross-Regional HTTPS Internal LB."
)
- purpose = "REGIONAL_MANAGED_PROXY"
- role = each.value.active != false ? "ACTIVE" : "BACKUP"
+ purpose = each.value.global ? "GLOBAL_MANAGED_PROXY" : "REGIONAL_MANAGED_PROXY"
+ role = each.value.active ? "ACTIVE" : "BACKUP"
}
resource "google_compute_subnetwork" "psc" {
@@ -144,34 +191,31 @@ resource "google_compute_subnetwork" "psc" {
name = each.value.name
region = each.value.region
ip_cidr_range = each.value.ip_cidr_range
- description = (
- each.value.description == null
- ? "Terraform-managed subnet for Private Service Connect (PSC NAT)."
- : each.value.description
+ description = coalesce(
+ each.value.description,
+ "Terraform-managed subnet for Private Service Connect (PSC NAT)."
)
purpose = "PRIVATE_SERVICE_CONNECT"
}
+
resource "google_compute_subnetwork_iam_binding" "authoritative" {
for_each = {
for binding in local.subnet_iam :
"${binding.subnet}.${binding.role}" => binding
}
project = var.project_id
- subnetwork = google_compute_subnetwork.subnetwork[each.value.subnet].name
- region = google_compute_subnetwork.subnetwork[each.value.subnet].region
+ subnetwork = local.all_subnets[each.value.subnet].name
+ region = local.all_subnets[each.value.subnet].region
role = each.value.role
members = each.value.members
}
resource "google_compute_subnetwork_iam_binding" "bindings" {
- for_each = {
- for binding in local.subnet_iam_bindings :
- "${binding.subnet}.${binding.role}.${try(binding.condition.title, "")}" => binding
- }
+ for_each = local.subnet_iam_bindings
project = var.project_id
- subnetwork = google_compute_subnetwork.subnetwork[each.value.subnet].name
- region = google_compute_subnetwork.subnetwork[each.value.subnet].region
+ subnetwork = local.all_subnets[each.value.subnet].name
+ region = local.all_subnets[each.value.subnet].region
role = each.value.role
members = each.value.members
dynamic "condition" {
@@ -184,13 +228,11 @@ resource "google_compute_subnetwork_iam_binding" "bindings" {
}
}
-# TODO: merge factory subnet IAM members
-
resource "google_compute_subnetwork_iam_member" "bindings" {
- for_each = var.subnet_iam_bindings_additive
+ for_each = local.subnet_iam_bindings_additive
project = var.project_id
- subnetwork = google_compute_subnetwork.subnetwork[each.value.subnet].name
- region = google_compute_subnetwork.subnetwork[each.value.subnet].region
+ subnetwork = local.all_subnets[each.value.subnet].name
+ region = local.all_subnets[each.value.subnet].region
role = each.value.role
member = each.value.member
dynamic "condition" {
diff --git a/modules/net-vpc/variables.tf b/modules/net-vpc/variables.tf
index 3837c9b0..5c4cc692 100644
--- a/modules/net-vpc/variables.tf
+++ b/modules/net-vpc/variables.tf
@@ -31,12 +31,6 @@ variable "create_googleapis_routes" {
default = {}
}
-variable "data_folder" {
- description = "An optional folder containing the subnet configurations in YaML format."
- type = string
- default = null
-}
-
variable "delete_default_routes_on_create" {
description = "Set to true to delete the default routes at creation time."
type = bool
@@ -62,6 +56,14 @@ variable "dns_policy" {
default = null
}
+variable "factories_config" {
+ description = "Paths to data files and folders that enable factory functionality."
+ type = object({
+ subnets_folder = string
+ })
+ default = null
+}
+
variable "firewall_policy_enforcement_order" {
description = "Order that Firewall Rules and Firewall Policies are evaluated. Can be either 'BEFORE_CLASSIC_FIREWALL' or 'AFTER_CLASSIC_FIREWALL'."
type = string
@@ -164,42 +166,6 @@ variable "shared_vpc_service_projects" {
default = []
}
-variable "subnet_iam" {
- description = "Subnet IAM bindings in {REGION/NAME => {ROLE => [MEMBERS]} format."
- type = map(map(list(string)))
- default = {}
-}
-
-variable "subnet_iam_bindings" {
- description = "Authoritative IAM bindings in {REGION/NAME => {ROLE => {members = [], condition = {}}}}."
- type = map(map(object({
- members = list(string)
- condition = optional(object({
- expression = string
- title = string
- description = optional(string)
- }))
- })))
- nullable = false
- default = {}
-}
-
-variable "subnet_iam_bindings_additive" {
- description = "Individual additive IAM bindings. Keys are arbitrary."
- type = map(object({
- member = string
- role = string
- subnet = string
- condition = optional(object({
- expression = string
- title = string
- description = optional(string)
- }))
- }))
- nullable = false
- default = {}
-}
-
variable "subnets" {
description = "Subnet configuration."
type = list(object({
@@ -222,20 +188,63 @@ variable "subnets" {
# enable_private_access = optional(string)
}))
secondary_ip_ranges = optional(map(string))
+
+ iam = optional(map(list(string)), {})
+ iam_bindings = optional(map(object({
+ role = string
+ members = list(string)
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
+ iam_bindings_additive = optional(map(object({
+ member = string
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
}))
- default = []
+ default = []
+ nullable = false
}
variable "subnets_proxy_only" {
- description = "List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active."
+ description = "List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active."
type = list(object({
name = string
ip_cidr_range = string
region = string
description = optional(string)
- active = bool
+ active = optional(bool, true)
+ global = optional(bool, false)
+
+ iam = optional(map(list(string)), {})
+ iam_bindings = optional(map(object({
+ role = string
+ members = list(string)
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
+ iam_bindings_additive = optional(map(object({
+ member = string
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
}))
- default = []
+ default = []
+ nullable = false
}
variable "subnets_psc" {
@@ -245,8 +254,29 @@ variable "subnets_psc" {
ip_cidr_range = string
region = string
description = optional(string)
+
+ iam = optional(map(list(string)), {})
+ iam_bindings = optional(map(object({
+ role = string
+ members = list(string)
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
+ iam_bindings_additive = optional(map(object({
+ member = string
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
}))
- default = []
+ default = []
+ nullable = false
}
variable "vpc_create" {
diff --git a/modules/net-vpc/versions.tf b/modules/net-vpc/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-vpc/versions.tf
+++ b/modules/net-vpc/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-vpn-dynamic/versions.tf b/modules/net-vpn-dynamic/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-vpn-dynamic/versions.tf
+++ b/modules/net-vpn-dynamic/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-vpn-ha/versions.tf b/modules/net-vpn-ha/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-vpn-ha/versions.tf
+++ b/modules/net-vpn-ha/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/net-vpn-static/versions.tf b/modules/net-vpn-static/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/net-vpn-static/versions.tf
+++ b/modules/net-vpn-static/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/organization/README.md b/modules/organization/README.md
index eb228dcf..fd9ca094 100644
--- a/modules/organization/README.md
+++ b/modules/organization/README.md
@@ -446,24 +446,24 @@ module "org" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [organization_id](variables.tf#L210) | Organization id in organizations/nnnnnn format. | string
| ✓ | |
+| [organization_id](variables.tf#L211) | Organization id in organizations/nnnnnn format. | string
| ✓ | |
| [contacts](variables.tf#L17) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string))
| | {}
|
| [custom_roles](variables.tf#L24) | Map of role name => list of permissions to create in this project. | map(list(string))
| | {}
|
| [firewall_policy](variables.tf#L31) | Hierarchical firewall policies to associate to the organization. | object({…})
| | null
|
| [group_iam](variables.tf#L40) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string))
| | {}
|
| [iam](variables.tf#L47) | IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
-| [iam_bindings](variables.tf#L54) | Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}. | map(object({…}))
| | {}
|
-| [iam_bindings_additive](variables.tf#L68) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
-| [logging_data_access](variables.tf#L83) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string)))
| | {}
|
-| [logging_exclusions](variables.tf#L98) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string)
| | {}
|
-| [logging_sinks](variables.tf#L105) | Logging sinks to create for the organization. | map(object({…}))
| | {}
|
-| [network_tags](variables.tf#L135) | 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#L157) | Organization policies applied to this organization keyed by policy name. | map(object({…}))
| | {}
|
-| [org_policies_data_path](variables.tf#L184) | Path containing org policies in YAML format. | string
| | null
|
-| [org_policy_custom_constraints](variables.tf#L190) | Organization policy custom constraints keyed by constraint name. | map(object({…}))
| | {}
|
-| [org_policy_custom_constraints_data_path](variables.tf#L204) | Path containing org policy custom constraints in YAML format. | string
| | null
|
-| [tag_bindings](variables.tf#L219) | Tag bindings for this organization, in key => tag value id format. | map(string)
| | null
|
-| [tags](variables.tf#L225) | 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({…}))
| | {}
|
+| [iam_bindings](variables.tf#L54) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [iam_bindings_additive](variables.tf#L69) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [logging_data_access](variables.tf#L84) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string)))
| | {}
|
+| [logging_exclusions](variables.tf#L99) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string)
| | {}
|
+| [logging_sinks](variables.tf#L106) | Logging sinks to create for the organization. | map(object({…}))
| | {}
|
+| [network_tags](variables.tf#L136) | 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#L158) | Organization policies applied to this organization keyed by policy name. | map(object({…}))
| | {}
|
+| [org_policies_data_path](variables.tf#L185) | Path containing org policies in YAML format. | string
| | null
|
+| [org_policy_custom_constraints](variables.tf#L191) | Organization policy custom constraints keyed by constraint name. | map(object({…}))
| | {}
|
+| [org_policy_custom_constraints_data_path](variables.tf#L205) | Path containing org policy custom constraints in YAML format. | string
| | null
|
+| [tag_bindings](variables.tf#L220) | Tag bindings for this organization, in key => tag value id format. | map(string)
| | null
|
+| [tags](variables.tf#L226) | 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
diff --git a/modules/organization/iam.tf b/modules/organization/iam.tf
index 2882d02a..81a8d2b0 100644
--- a/modules/organization/iam.tf
+++ b/modules/organization/iam.tf
@@ -51,7 +51,7 @@ resource "google_organization_iam_binding" "authoritative" {
resource "google_organization_iam_binding" "bindings" {
for_each = var.iam_bindings
org_id = local.organization_id_numeric
- role = each.key
+ role = each.value.role
members = each.value.members
dynamic "condition" {
for_each = each.value.condition == null ? [] : [""]
diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf
index 99fe49c6..c9899e2e 100644
--- a/modules/organization/variables.tf
+++ b/modules/organization/variables.tf
@@ -52,9 +52,10 @@ variable "iam" {
}
variable "iam_bindings" {
- description = "Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}."
+ description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary."
type = map(object({
members = list(string)
+ role = string
condition = optional(object({
expression = string
title = string
diff --git a/modules/organization/versions.tf b/modules/organization/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/organization/versions.tf
+++ b/modules/organization/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/project/README.md b/modules/project/README.md
index 7479eca8..3fddf95f 100644
--- a/modules/project/README.md
+++ b/modules/project/README.md
@@ -117,10 +117,11 @@ module "project" {
"stackdriver.googleapis.com"
]
iam_bindings = {
- "roles/resourcemanager.projectIamAdmin" = {
+ iam_admin_conditional = {
members = [
"group:test-admins@example.org"
]
+ role = "roles/resourcemanager.projectIamAdmin"
condition = {
title = "delegated_network_user_one"
expression = <<-END
@@ -589,7 +590,7 @@ output "compute_robot" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L185) | Project name and id suffix. | string
| ✓ | |
+| [name](variables.tf#L186) | Project name and id suffix. | string
| ✓ | |
| [auto_create_network](variables.tf#L17) | Whether to create the default network for the project. | bool
| | false
|
| [billing_account](variables.tf#L23) | Billing account id. | string
| | null
|
| [compute_metadata](variables.tf#L29) | Optional compute metadata key/values. Only usable if compute API has been enabled. | map(string)
| | {}
|
@@ -599,28 +600,28 @@ output "compute_robot" {
| [descriptive_name](variables.tf#L63) | Name of the project name. Used for project name instead of `name` variable. | string
| | null
|
| [group_iam](variables.tf#L69) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string))
| | {}
|
| [iam](variables.tf#L76) | Authoritative IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
-| [iam_bindings](variables.tf#L83) | Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}. | map(object({…}))
| | {}
|
-| [iam_bindings_additive](variables.tf#L97) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
-| [labels](variables.tf#L112) | Resource labels. | map(string)
| | {}
|
-| [lien_reason](variables.tf#L119) | If non-empty, creates a project lien with this description. | string
| | null
|
-| [logging_data_access](variables.tf#L125) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string)))
| | {}
|
-| [logging_exclusions](variables.tf#L140) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string)
| | {}
|
-| [logging_sinks](variables.tf#L147) | Logging sinks to create for this project. | map(object({…}))
| | {}
|
-| [metric_scopes](variables.tf#L178) | List of projects that will act as metric scopes for this project. | list(string)
| | []
|
-| [org_policies](variables.tf#L190) | Organization policies applied to this project keyed by policy name. | map(object({…}))
| | {}
|
-| [org_policies_data_path](variables.tf#L217) | Path containing org policies in YAML format. | string
| | null
|
-| [parent](variables.tf#L223) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string
| | null
|
-| [prefix](variables.tf#L233) | Optional prefix used to generate project id and name. | string
| | null
|
-| [project_create](variables.tf#L243) | Create project. When set to false, uses a data source to reference existing project. | bool
| | true
|
-| [service_config](variables.tf#L249) | Configure service API activation. | object({…})
| | {…}
|
-| [service_encryption_key_ids](variables.tf#L261) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | map(list(string))
| | {}
|
-| [service_perimeter_bridges](variables.tf#L268) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | list(string)
| | null
|
-| [service_perimeter_standard](variables.tf#L275) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | string
| | null
|
-| [services](variables.tf#L281) | Service APIs to enable. | list(string)
| | []
|
-| [shared_vpc_host_config](variables.tf#L287) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…})
| | null
|
-| [shared_vpc_service_config](variables.tf#L296) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…})
| | {…}
|
-| [skip_delete](variables.tf#L318) | Allows the underlying resources to be destroyed without destroying the project itself. | bool
| | false
|
-| [tag_bindings](variables.tf#L324) | Tag bindings for this project, in key => tag value id format. | map(string)
| | null
|
+| [iam_bindings](variables.tf#L83) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [iam_bindings_additive](variables.tf#L98) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [labels](variables.tf#L113) | Resource labels. | map(string)
| | {}
|
+| [lien_reason](variables.tf#L120) | If non-empty, creates a project lien with this description. | string
| | null
|
+| [logging_data_access](variables.tf#L126) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string)))
| | {}
|
+| [logging_exclusions](variables.tf#L141) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string)
| | {}
|
+| [logging_sinks](variables.tf#L148) | Logging sinks to create for this project. | map(object({…}))
| | {}
|
+| [metric_scopes](variables.tf#L179) | List of projects that will act as metric scopes for this project. | list(string)
| | []
|
+| [org_policies](variables.tf#L191) | Organization policies applied to this project keyed by policy name. | map(object({…}))
| | {}
|
+| [org_policies_data_path](variables.tf#L218) | Path containing org policies in YAML format. | string
| | null
|
+| [parent](variables.tf#L224) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string
| | null
|
+| [prefix](variables.tf#L234) | Optional prefix used to generate project id and name. | string
| | null
|
+| [project_create](variables.tf#L244) | Create project. When set to false, uses a data source to reference existing project. | bool
| | true
|
+| [service_config](variables.tf#L250) | Configure service API activation. | object({…})
| | {…}
|
+| [service_encryption_key_ids](variables.tf#L262) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | map(list(string))
| | {}
|
+| [service_perimeter_bridges](variables.tf#L269) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | list(string)
| | null
|
+| [service_perimeter_standard](variables.tf#L276) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | string
| | null
|
+| [services](variables.tf#L282) | Service APIs to enable. | list(string)
| | []
|
+| [shared_vpc_host_config](variables.tf#L288) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…})
| | null
|
+| [shared_vpc_service_config](variables.tf#L297) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…})
| | {…}
|
+| [skip_delete](variables.tf#L319) | Allows the underlying resources to be destroyed without destroying the project itself. | bool
| | false
|
+| [tag_bindings](variables.tf#L325) | Tag bindings for this project, in key => tag value id format. | map(string)
| | null
|
## Outputs
diff --git a/modules/project/iam.tf b/modules/project/iam.tf
index 16f187d6..0f00f286 100644
--- a/modules/project/iam.tf
+++ b/modules/project/iam.tf
@@ -58,7 +58,7 @@ resource "google_project_iam_binding" "authoritative" {
resource "google_project_iam_binding" "bindings" {
for_each = var.iam_bindings
project = local.project.project_id
- role = each.key
+ role = each.value.role
members = each.value.members
dynamic "condition" {
for_each = each.value.condition == null ? [] : [""]
diff --git a/modules/project/service-agents.yaml b/modules/project/service-agents.yaml
index 4ef3cafd..c8eff2df 100644
--- a/modules/project/service-agents.yaml
+++ b/modules/project/service-agents.yaml
@@ -221,6 +221,7 @@
service_agent: "service-%s@gcp-sa-healthcare.iam.gserviceaccount.com"
- name: "iap"
service_agent: "service-%s@gcp-sa-iap.iam.gserviceaccount.com"
+ jit: true
- name: "identitytoolkit"
service_agent: "service-%s@gcp-sa-identitytoolkit.iam.gserviceaccount.com"
- name: "ids"
diff --git a/modules/project/variables.tf b/modules/project/variables.tf
index 2824fcf3..68f8b6c0 100644
--- a/modules/project/variables.tf
+++ b/modules/project/variables.tf
@@ -81,9 +81,10 @@ variable "iam" {
}
variable "iam_bindings" {
- description = "Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}."
+ description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary."
type = map(object({
members = list(string)
+ role = string
condition = optional(object({
expression = string
title = string
diff --git a/modules/project/versions.tf b/modules/project/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/project/versions.tf
+++ b/modules/project/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/projects-data-source/versions.tf b/modules/projects-data-source/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/projects-data-source/versions.tf
+++ b/modules/projects-data-source/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/pubsub/README.md b/modules/pubsub/README.md
index 44a0e737..69a18dbe 100644
--- a/modules/pubsub/README.md
+++ b/modules/pubsub/README.md
@@ -61,16 +61,10 @@ module "pubsub" {
project_id = "my-project"
name = "my-topic"
subscriptions = {
- test-pull = null
+ test-pull = {}
test-pull-override = {
- labels = { test = "override" }
- options = {
- ack_deadline_seconds = null
- message_retention_duration = null
- retain_acked_messages = true
- expiration_policy_ttl = null
- filter = null
- }
+ labels = { test = "override" }
+ retain_acked_messages = true
}
}
}
@@ -87,13 +81,10 @@ module "pubsub" {
project_id = "my-project"
name = "my-topic"
subscriptions = {
- test-push = null
- }
- push_configs = {
test-push = {
- endpoint = "https://example.com/foo"
- attributes = null
- oidc_token = null
+ push = {
+ endpoint = "https://example.com/foo"
+ }
}
}
}
@@ -110,20 +101,45 @@ module "pubsub" {
project_id = "my-project"
name = "my-topic"
subscriptions = {
- test-bigquery = null
- }
- bigquery_subscription_configs = {
test-bigquery = {
- table = "my_project_id:my_dataset.my_table"
- use_topic_schema = true
- write_metadata = false
- drop_unknown_fields = true
+ bigquery = {
+ table = "my_project_id:my_dataset.my_table"
+ use_topic_schema = true
+ write_metadata = false
+ drop_unknown_fields = true
+ }
}
}
}
# tftest modules=1 resources=2
```
+### Cloud Storage subscriptions
+
+Cloud Storage subscriptions need extra configuration in the `cloud_storage_subscription_configs` variable.
+
+```hcl
+module "pubsub" {
+ source = "./fabric/modules/pubsub"
+ project_id = "my-project"
+ name = "my-topic"
+ subscriptions = {
+ test-cloudstorage = {
+ cloud_storage = {
+ bucket = "my-bucket"
+ filename_prefix = "test_prefix"
+ filename_suffix = "test_suffix"
+ max_duration = "100s"
+ max_bytes = 1000
+ avro_config = {
+ write_metadata = true
+ }
+ }
+ }
+ }
+}
+# tftest modules=1 resources=2
+```
### Subscriptions with IAM
```hcl
@@ -132,47 +148,40 @@ module "pubsub" {
project_id = "my-project"
name = "my-topic"
subscriptions = {
- test-1 = null
- test-1 = null
- }
- subscription_iam = {
test-1 = {
- "roles/pubsub.subscriber" = ["user:user1@ludomagno.net"]
+ iam = {
+ "roles/pubsub.subscriber" = ["user:user1@example.com"]
+ }
}
}
}
# tftest modules=1 resources=3
```
-
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L79) | PubSub topic name. | string
| ✓ | |
-| [project_id](variables.tf#L84) | Project used for resources. | string
| ✓ | |
-| [bigquery_subscription_configs](variables.tf#L17) | Configuration parameters for BigQuery subscriptions. | map(object({…}))
| | {}
|
-| [dead_letter_configs](variables.tf#L28) | Per-subscription dead letter policy configuration. | map(object({…}))
| | {}
|
-| [defaults](variables.tf#L37) | Subscription defaults for options. | object({…})
| | {…}
|
-| [iam](variables.tf#L55) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
-| [kms_key](variables.tf#L61) | KMS customer managed encryption key. | string
| | null
|
-| [labels](variables.tf#L67) | Labels. | map(string)
| | {}
|
-| [message_retention_duration](variables.tf#L73) | Minimum duration to retain a message after it is published to the topic. | string
| | null
|
-| [push_configs](variables.tf#L89) | Push subscription configurations. | map(object({…}))
| | {}
|
-| [regions](variables.tf#L102) | List of regions used to set persistence policy. | list(string)
| | []
|
-| [schema](variables.tf#L108) | Topic schema. If set, all messages in this topic should follow this schema. | object({…})
| | null
|
-| [subscription_iam](variables.tf#L118) | IAM bindings for subscriptions in {SUBSCRIPTION => {ROLE => [MEMBERS]}} format. | map(map(list(string)))
| | {}
|
-| [subscriptions](variables.tf#L124) | Topic subscriptions. Also define push configs for push subscriptions. If options is set to null subscription defaults will be used. Labels default to topic labels if set to null. | map(object({…}))
| | {}
|
+| [name](variables.tf#L73) | PubSub topic name. | string
| ✓ | |
+| [project_id](variables.tf#L78) | Project used for resources. | string
| ✓ | |
+| [iam](variables.tf#L17) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
+| [iam_bindings](variables.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [iam_bindings_additive](variables.tf#L39) | Keyring individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [kms_key](variables.tf#L54) | KMS customer managed encryption key. | string
| | null
|
+| [labels](variables.tf#L60) | Labels. | map(string)
| | {}
|
+| [message_retention_duration](variables.tf#L67) | Minimum duration to retain a message after it is published to the topic. | string
| | null
|
+| [regions](variables.tf#L83) | List of regions used to set persistence policy. | list(string)
| | []
|
+| [schema](variables.tf#L90) | Topic schema. If set, all messages in this topic should follow this schema. | object({…})
| | null
|
+| [subscriptions](variables.tf#L100) | Topic subscriptions. Also define push configs for push subscriptions. If options is set to null subscription defaults will be used. Labels default to topic labels if set to null. | map(object({…}))
| | {}
|
## Outputs
| name | description | sensitive |
|---|---|:---:|
| [id](outputs.tf#L17) | Fully qualified topic id. | |
-| [schema](outputs.tf#L26) | Schema resource. | |
-| [schema_id](outputs.tf#L31) | Schema resource id. | |
-| [subscription_id](outputs.tf#L36) | Subscription ids. | |
-| [subscriptions](outputs.tf#L46) | Subscription resources. | |
-| [topic](outputs.tf#L54) | Topic resource. | |
-
+| [schema](outputs.tf#L27) | Schema resource. | |
+| [schema_id](outputs.tf#L32) | Schema resource id. | |
+| [subscription_id](outputs.tf#L37) | Subscription ids. | |
+| [subscriptions](outputs.tf#L48) | Subscription resources. | |
+| [topic](outputs.tf#L57) | Topic resource. | |
diff --git a/modules/pubsub/iam.tf b/modules/pubsub/iam.tf
new file mode 100644
index 00000000..4e39b43a
--- /dev/null
+++ b/modules/pubsub/iam.tf
@@ -0,0 +1,140 @@
+/**
+ * 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 authoritative.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 {
+ subscription_iam = flatten([
+ for k, v in var.subscriptions : [
+ for role, members in v.iam : {
+ subscription = k
+ role = role
+ members = members
+ }
+ ]
+ ])
+ subscription_iam_bindings = merge([
+ for k, v in var.subscriptions : {
+ for binding_key, data in v.iam_bindings :
+ binding_key => {
+ subscription = k
+ role = data.role
+ members = data.members
+ condition = data.condition
+ }
+ }
+ ]...)
+ subscription_iam_bindings_additive = merge([
+ for k, v in var.subscriptions : {
+ for binding_key, data in v.iam_bindings_additive :
+ binding_key => {
+ subscription = k
+ role = data.role
+ member = data.member
+ condition = data.condition
+ }
+ }
+ ]...)
+}
+
+moved {
+ from = google_pubsub_topic_iam_binding.default
+ to = google_pubsub_topic_iam_binding.authoritative
+}
+
+resource "google_pubsub_topic_iam_binding" "authoritative" {
+ for_each = var.iam
+ project = var.project_id
+ topic = google_pubsub_topic.default.name
+ role = each.key
+ members = each.value
+}
+
+resource "google_pubsub_topic_iam_binding" "bindings" {
+ for_each = var.iam_bindings
+ topic = google_pubsub_topic.default.name
+ role = each.value.role
+ members = each.value.members
+ dynamic "condition" {
+ for_each = each.value.condition == null ? [] : [""]
+ content {
+ expression = each.value.condition.expression
+ title = each.value.condition.title
+ description = each.value.condition.description
+ }
+ }
+}
+
+resource "google_pubsub_topic_iam_member" "bindings" {
+ for_each = var.iam_bindings_additive
+ topic = google_pubsub_topic.default.name
+ role = each.value.role
+ member = each.value.member
+ dynamic "condition" {
+ for_each = each.value.condition == null ? [] : [""]
+ content {
+ expression = each.value.condition.expression
+ title = each.value.condition.title
+ description = each.value.condition.description
+ }
+ }
+}
+
+moved {
+ from = google_pubsub_subscription_iam_binding.default
+ to = google_pubsub_subscription_iam_binding.authoritative
+}
+
+resource "google_pubsub_subscription_iam_binding" "authoritative" {
+ for_each = {
+ for binding in local.subscription_iam :
+ "${binding.subscription}.${binding.role}" => binding
+ }
+ project = var.project_id
+ subscription = google_pubsub_subscription.default[each.value.subscription].name
+ role = each.value.role
+ members = each.value.members
+}
+
+resource "google_pubsub_subscription_iam_binding" "bindings" {
+ for_each = local.subscription_iam_bindings
+ project = var.project_id
+ subscription = google_pubsub_subscription.default[each.value.subscription].name
+ role = each.value.role
+ members = each.value.members
+ dynamic "condition" {
+ for_each = each.value.condition == null ? [] : [""]
+ content {
+ expression = each.value.condition.expression
+ title = each.value.condition.title
+ description = each.value.condition.description
+ }
+ }
+}
+
+resource "google_pubsub_subscription_iam_member" "members" {
+ for_each = local.subscription_iam_bindings_additive
+ project = var.project_id
+ subscription = google_pubsub_subscription.default[each.value.subscription].name
+ role = each.value.role
+ member = each.value.member
+ dynamic "condition" {
+ for_each = each.value.condition == null ? [] : [""]
+ content {
+ expression = each.value.condition.expression
+ title = each.value.condition.title
+ description = each.value.condition.description
+ }
+ }
+}
diff --git a/modules/pubsub/main.tf b/modules/pubsub/main.tf
index ccb6f5d7..de065029 100644
--- a/modules/pubsub/main.tf
+++ b/modules/pubsub/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,24 +15,6 @@
*/
locals {
- sub_iam_members = flatten([
- for sub, roles in var.subscription_iam : [
- for role, members in roles : {
- sub = sub
- role = role
- members = members
- }
- ]
- ])
- oidc_config = {
- for k, v in var.push_configs : k => v.oidc_token
- }
- subscriptions = {
- for k, v in var.subscriptions : k => {
- labels = try(v.labels, v, null) == null ? var.labels : v.labels
- options = try(v.options, v, null) == null ? var.defaults : v.options
- }
- }
topic_id_static = "projects/${var.project_id}/topics/${var.name}"
}
@@ -67,75 +49,73 @@ resource "google_pubsub_topic" "default" {
}
}
-resource "google_pubsub_topic_iam_binding" "default" {
- for_each = var.iam
- project = var.project_id
- topic = google_pubsub_topic.default.name
- role = each.key
- members = each.value
-}
-
resource "google_pubsub_subscription" "default" {
- for_each = local.subscriptions
- project = var.project_id
- name = each.key
- topic = google_pubsub_topic.default.name
- labels = each.value.labels
- ack_deadline_seconds = each.value.options.ack_deadline_seconds
- message_retention_duration = each.value.options.message_retention_duration
- retain_acked_messages = each.value.options.retain_acked_messages
- filter = each.value.options.filter
+ for_each = var.subscriptions
+ project = var.project_id
+ name = each.key
+ topic = google_pubsub_topic.default.name
+ labels = each.value.labels
+ ack_deadline_seconds = each.value.ack_deadline_seconds
+ message_retention_duration = each.value.message_retention_duration
+ retain_acked_messages = each.value.retain_acked_messages
+ filter = each.value.filter
+ enable_message_ordering = each.value.enable_message_ordering
+ enable_exactly_once_delivery = each.value.enable_exactly_once_delivery
dynamic "expiration_policy" {
- for_each = each.value.options.expiration_policy_ttl == null ? [] : [""]
+ for_each = each.value.expiration_policy_ttl == null ? [] : [""]
content {
- ttl = each.value.options.expiration_policy_ttl
+ ttl = each.value.expiration_policy_ttl
}
}
dynamic "dead_letter_policy" {
- for_each = try(var.dead_letter_configs[each.key], null) == null ? [] : [""]
+ for_each = each.value.dead_letter_policy == null ? [] : [""]
content {
- dead_letter_topic = var.dead_letter_configs[each.key].topic
- max_delivery_attempts = var.dead_letter_configs[each.key].max_delivery_attempts
+ dead_letter_topic = each.value.dead_letter_policy.topic
+ max_delivery_attempts = each.value.dead_letter_policy.max_delivery_attempts
}
}
dynamic "push_config" {
- for_each = try(var.push_configs[each.key], null) == null ? [] : [""]
+ for_each = each.value.push == null ? [] : [""]
content {
- push_endpoint = var.push_configs[each.key].endpoint
- attributes = var.push_configs[each.key].attributes
+ push_endpoint = each.value.push.endpoint
+ attributes = each.value.push.attributes
dynamic "oidc_token" {
- for_each = (
- local.oidc_config[each.key] == null ? [] : [""]
- )
+ for_each = each.value.push.oidc_token == null ? [] : [""]
content {
- service_account_email = local.oidc_config[each.key].service_account_email
- audience = local.oidc_config[each.key].audience
+ service_account_email = each.value.push.oidc_token.service_account_email
+ audience = each.value.push.oidc_token.audience
}
}
}
}
dynamic "bigquery_config" {
- for_each = try(var.bigquery_subscription_configs[each.key], null) == null ? [] : [""]
+ for_each = each.value.bigquery == null ? [] : [""]
content {
- table = var.bigquery_subscription_configs[each.key].table
- use_topic_schema = var.bigquery_subscription_configs[each.key].use_topic_schema
- write_metadata = var.bigquery_subscription_configs[each.key].write_metadata
- drop_unknown_fields = var.bigquery_subscription_configs[each.key].drop_unknown_fields
+ table = each.value.bigquery.table
+ use_topic_schema = each.value.bigquery.use_topic_schema
+ write_metadata = each.value.bigquery.write_metadata
+ drop_unknown_fields = each.value.bigquery.drop_unknown_fields
+ }
+ }
+
+ dynamic "cloud_storage_config" {
+ for_each = each.value.cloud_storage == null ? [] : [""]
+ content {
+ bucket = each.value.cloud_storage.bucket
+ filename_prefix = each.value.cloud_storage.filename_prefix
+ filename_suffix = each.value.cloud_storage.filename_suffix
+ max_duration = each.value.cloud_storage.max_duration
+ max_bytes = each.value.cloud_storage.max_bytes
+ dynamic "avro_config" {
+ for_each = each.value.cloud_storage.avro_config == null ? [] : [""]
+ content {
+ write_metadata = each.value.cloud_storage.avro_config.write_metadata
+ }
+ }
}
}
}
-
-resource "google_pubsub_subscription_iam_binding" "default" {
- for_each = {
- for binding in local.sub_iam_members :
- "${binding.sub}.${binding.role}" => binding
- }
- project = var.project_id
- subscription = google_pubsub_subscription.default[each.value.sub].name
- role = each.value.role
- members = each.value.members
-}
diff --git a/modules/pubsub/outputs.tf b/modules/pubsub/outputs.tf
index 0d149302..8218e2b3 100644
--- a/modules/pubsub/outputs.tf
+++ b/modules/pubsub/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.
@@ -19,7 +19,8 @@ output "id" {
value = local.topic_id_static
depends_on = [
google_pubsub_topic.default,
- google_pubsub_topic_iam_binding.default
+ google_pubsub_topic_iam_binding.authoritative,
+ google_pubsub_topic_iam_binding.bindings
]
}
@@ -39,7 +40,8 @@ output "subscription_id" {
for k, v in google_pubsub_subscription.default : k => v.id
}
depends_on = [
- google_pubsub_subscription_iam_binding.default
+ google_pubsub_subscription_iam_binding.authoritative,
+ google_pubsub_subscription_iam_binding.bindings
]
}
@@ -47,7 +49,8 @@ output "subscriptions" {
description = "Subscription resources."
value = google_pubsub_subscription.default
depends_on = [
- google_pubsub_subscription_iam_binding.default
+ google_pubsub_subscription_iam_binding.authoritative,
+ google_pubsub_subscription_iam_binding.bindings
]
}
@@ -55,6 +58,7 @@ output "topic" {
description = "Topic resource."
value = google_pubsub_topic.default
depends_on = [
- google_pubsub_topic_iam_binding.default
+ google_pubsub_topic_iam_binding.authoritative,
+ google_pubsub_topic_iam_binding.bindings
]
}
diff --git a/modules/pubsub/variables.tf b/modules/pubsub/variables.tf
index afefb4a8..370c42fa 100644
--- a/modules/pubsub/variables.tf
+++ b/modules/pubsub/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,48 +14,41 @@
* limitations under the License.
*/
-variable "bigquery_subscription_configs" {
- description = "Configuration parameters for BigQuery subscriptions."
- type = map(object({
- table = string
- use_topic_schema = bool
- write_metadata = bool
- drop_unknown_fields = bool
- }))
- default = {}
-}
-
-variable "dead_letter_configs" {
- description = "Per-subscription dead letter policy configuration."
- type = map(object({
- topic = string
- max_delivery_attempts = number
- }))
- default = {}
-}
-
-variable "defaults" {
- description = "Subscription defaults for options."
- type = object({
- ack_deadline_seconds = number
- message_retention_duration = string
- retain_acked_messages = bool
- expiration_policy_ttl = string
- filter = string
- })
- default = {
- ack_deadline_seconds = null
- message_retention_duration = null
- retain_acked_messages = null
- expiration_policy_ttl = null
- filter = null
- }
-}
-
variable "iam" {
description = "IAM bindings for topic in {ROLE => [MEMBERS]} format."
type = map(list(string))
default = {}
+ nullable = false
+}
+
+variable "iam_bindings" {
+ description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary."
+ type = map(object({
+ members = list(string)
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ }))
+ nullable = false
+ default = {}
+}
+
+variable "iam_bindings_additive" {
+ description = "Keyring individual additive IAM bindings. Keys are arbitrary."
+ type = map(object({
+ member = string
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ }))
+ nullable = false
+ default = {}
}
variable "kms_key" {
@@ -68,6 +61,7 @@ variable "labels" {
description = "Labels."
type = map(string)
default = {}
+ nullable = false
}
variable "message_retention_duration" {
@@ -86,23 +80,11 @@ variable "project_id" {
type = string
}
-variable "push_configs" {
- description = "Push subscription configurations."
- type = map(object({
- attributes = map(string)
- endpoint = string
- oidc_token = object({
- audience = string
- service_account_email = string
- })
- }))
- default = {}
-}
-
variable "regions" {
description = "List of regions used to set persistence policy."
type = list(string)
default = []
+ nullable = false
}
variable "schema" {
@@ -115,23 +97,72 @@ variable "schema" {
default = null
}
-variable "subscription_iam" {
- description = "IAM bindings for subscriptions in {SUBSCRIPTION => {ROLE => [MEMBERS]}} format."
- type = map(map(list(string)))
- default = {}
-}
-
variable "subscriptions" {
description = "Topic subscriptions. Also define push configs for push subscriptions. If options is set to null subscription defaults will be used. Labels default to topic labels if set to null."
type = map(object({
- labels = map(string)
- options = object({
- ack_deadline_seconds = number
- message_retention_duration = string
- retain_acked_messages = bool
- expiration_policy_ttl = string
- filter = string
- })
+ labels = optional(map(string))
+ ack_deadline_seconds = optional(number)
+ message_retention_duration = optional(string)
+ retain_acked_messages = optional(bool, false)
+ expiration_policy_ttl = optional(string)
+ filter = optional(string)
+ enable_message_ordering = optional(bool, false)
+ enable_exactly_once_delivery = optional(bool, false)
+ dead_letter_policy = optional(object({
+ topic = string
+ max_delivery_attempts = optional(number)
+ }))
+ retry_policy = optional(object({
+ minimum_backoff = optional(number)
+ maximum_backoff = optional(number)
+ }))
+
+ bigquery = optional(object({
+ table = string
+ use_topic_schema = optional(bool, false)
+ write_metadata = optional(bool, false)
+ drop_unknown_fields = optional(bool, false)
+ }))
+ cloud_storage = optional(object({
+ bucket = string
+ filename_prefix = optional(string)
+ filename_suffix = optional(string)
+ max_duration = optional(string)
+ max_bytes = optional(number)
+ avro_config = optional(object({
+ write_metadata = optional(bool, false)
+ }))
+ }))
+ push = optional(object({
+ endpoint = string
+ attributes = optional(map(string))
+ no_wrapper = optional(bool, false)
+ oidc_token = optional(object({
+ audience = optional(string)
+ service_account_email = string
+ }))
+ }))
+
+ iam = optional(map(list(string)), {})
+ iam_bindings = optional(map(object({
+ members = list(string)
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
+ iam_bindings_additive = optional(map(object({
+ member = string
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ })), {})
}))
- default = {}
+ default = {}
+ nullable = false
}
diff --git a/modules/pubsub/versions.tf b/modules/pubsub/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/pubsub/versions.tf
+++ b/modules/pubsub/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/secret-manager/versions.tf b/modules/secret-manager/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/secret-manager/versions.tf
+++ b/modules/secret-manager/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/service-directory/versions.tf b/modules/service-directory/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/service-directory/versions.tf
+++ b/modules/service-directory/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/source-repository/README.md b/modules/source-repository/README.md
index a62013fa..c60ba7e4 100644
--- a/modules/source-repository/README.md
+++ b/modules/source-repository/README.md
@@ -75,13 +75,13 @@ module "repo" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L60) | Repository name. | string
| ✓ | |
-| [project_id](variables.tf#L65) | Project used for resources. | string
| ✓ | |
+| [name](variables.tf#L61) | Repository name. | string
| ✓ | |
+| [project_id](variables.tf#L66) | Project used for resources. | string
| ✓ | |
| [group_iam](variables.tf#L17) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string))
| | {}
|
| [iam](variables.tf#L24) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
-| [iam_bindings](variables.tf#L31) | Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}. | map(object({…}))
| | {}
|
-| [iam_bindings_additive](variables.tf#L45) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
-| [triggers](variables.tf#L70) | Cloud Build triggers. | map(object({…}))
| | {}
|
+| [iam_bindings](variables.tf#L31) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [iam_bindings_additive](variables.tf#L46) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [triggers](variables.tf#L71) | Cloud Build triggers. | map(object({…}))
| | {}
|
## Outputs
diff --git a/modules/source-repository/iam.tf b/modules/source-repository/iam.tf
index be0cf688..1b225d1b 100644
--- a/modules/source-repository/iam.tf
+++ b/modules/source-repository/iam.tf
@@ -44,7 +44,7 @@ resource "google_sourcerepo_repository_iam_binding" "bindings" {
for_each = var.iam_bindings
project = var.project_id
repository = google_sourcerepo_repository.default.name
- role = each.key
+ role = each.value.role
members = each.value.members
dynamic "condition" {
for_each = each.value.condition == null ? [] : [""]
diff --git a/modules/source-repository/variables.tf b/modules/source-repository/variables.tf
index ce1c34e7..23bfa789 100644
--- a/modules/source-repository/variables.tf
+++ b/modules/source-repository/variables.tf
@@ -29,9 +29,10 @@ variable "iam" {
}
variable "iam_bindings" {
- description = "Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}."
+ description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary."
type = map(object({
members = list(string)
+ role = string
condition = optional(object({
expression = string
title = string
diff --git a/modules/source-repository/versions.tf b/modules/source-repository/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/source-repository/versions.tf
+++ b/modules/source-repository/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/modules/vpc-sc/versions.tf b/modules/vpc-sc/versions.tf
index e4f7404f..91a91a31 100644
--- a/modules/vpc-sc/versions.tf
+++ b/modules/vpc-sc/versions.tf
@@ -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,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
- version = ">= 4.80.0" # tftest
+ version = ">= 4.82.0" # tftest
}
}
}
diff --git a/tests/blueprints/factories/project_factory/examples/example.yaml b/tests/blueprints/factories/project_factory/examples/example.yaml
index 5927caed..086fbd55 100644
--- a/tests/blueprints/factories/project_factory/examples/example.yaml
+++ b/tests/blueprints/factories/project_factory/examples/example.yaml
@@ -30,9 +30,10 @@ values:
module.project-factory.module.projects["prj-app-1"].google_project.project[0]:
auto_create_network: false
billing_account: 012345-67890A-BCDEF0
- folder_id: null
+ folder_id: "12345678"
labels:
app: app-1
+ environment: test
team: foo
name: test-pf-prj-app-1
org_id: null
@@ -61,9 +62,10 @@ values:
module.project-factory.module.projects["prj-app-2"].google_project.project[0]:
auto_create_network: false
billing_account: 012345-67890A-ABCDEF
- folder_id: null
+ folder_id: "12345678"
labels:
app: app-1
+ environment: test
team: foo
name: test-pf-prj-app-2
org_id: null
diff --git a/tests/examples/variables.tf b/tests/examples/variables.tf
index 3a5a3f75..9a65aa7a 100644
--- a/tests/examples/variables.tf
+++ b/tests/examples/variables.tf
@@ -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.
@@ -69,6 +69,7 @@ variable "vpc" {
default = {
name = "vpc_name"
self_link = "projects/xxx/global/networks/aaa"
+ id = "projects/xxx/global/networks/aaa"
}
}
diff --git a/tests/fast/stages/s2_networking_a_peering/stage.yaml b/tests/fast/stages/s2_networking_a_peering/stage.yaml
index 2c2ca3da..85b123af 100644
--- a/tests/fast/stages/s2_networking_a_peering/stage.yaml
+++ b/tests/fast/stages/s2_networking_a_peering/stage.yaml
@@ -14,4 +14,4 @@
counts:
modules: 28
- resources: 151
+ resources: 154
diff --git a/tests/fast/stages/s2_networking_b_vpn/stage.yaml b/tests/fast/stages/s2_networking_b_vpn/stage.yaml
index 9cb8ee83..831bcd50 100644
--- a/tests/fast/stages/s2_networking_b_vpn/stage.yaml
+++ b/tests/fast/stages/s2_networking_b_vpn/stage.yaml
@@ -14,4 +14,4 @@
counts:
modules: 30
- resources: 188
+ resources: 191
diff --git a/tests/fast/stages/s2_networking_c_nva/stage.yaml b/tests/fast/stages/s2_networking_c_nva/stage.yaml
index 3da9b352..e1ce4a05 100644
--- a/tests/fast/stages/s2_networking_c_nva/stage.yaml
+++ b/tests/fast/stages/s2_networking_c_nva/stage.yaml
@@ -14,4 +14,4 @@
counts:
modules: 42
- resources: 197
+ resources: 200
diff --git a/tests/fast/stages/s2_networking_d_separate_envs/stage.yaml b/tests/fast/stages/s2_networking_d_separate_envs/stage.yaml
index f60257c4..e2b6fe64 100644
--- a/tests/fast/stages/s2_networking_d_separate_envs/stage.yaml
+++ b/tests/fast/stages/s2_networking_d_separate_envs/stage.yaml
@@ -14,4 +14,4 @@
counts:
modules: 21
- resources: 168
+ resources: 170
diff --git a/tests/fast/stages/s2_networking_e_nva_bgp/stage.yaml b/tests/fast/stages/s2_networking_e_nva_bgp/stage.yaml
index 960ac523..bc557683 100644
--- a/tests/fast/stages/s2_networking_e_nva_bgp/stage.yaml
+++ b/tests/fast/stages/s2_networking_e_nva_bgp/stage.yaml
@@ -14,4 +14,4 @@
counts:
modules: 36
- resources: 208
+ resources: 211
diff --git a/tests/fast/stages/s3_project_factory/data/projects/project.yaml b/tests/fast/stages/s3_project_factory/data/projects/project.yaml
index 18b5cdb4..922b4044 100644
--- a/tests/fast/stages/s3_project_factory/data/projects/project.yaml
+++ b/tests/fast/stages/s3_project_factory/data/projects/project.yaml
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-parent_id: folders/012345678901
+parent: folders/012345678901
services:
- storage.googleapis.com
- stackdriver.googleapis.com
diff --git a/tests/modules/apigee/all_psc_mode.tfvars b/tests/modules/apigee/all_psc_mode.tfvars
new file mode 100644
index 00000000..41bafabb
--- /dev/null
+++ b/tests/modules/apigee/all_psc_mode.tfvars
@@ -0,0 +1,47 @@
+project_id = "my-project"
+organization = {
+ display_name = "My Organization"
+ description = "My Organization"
+ runtime_type = "CLOUD"
+ billing_type = "Pay-as-you-go"
+ database_encryption_key = "123456789"
+ analytics_region = "europe-west1"
+ disable_vpc_peering = true
+}
+envgroups = {
+ test = ["test.example.com"]
+ prod = ["prod.example.com"]
+}
+environments = {
+ apis-test = {
+ display_name = "APIs test"
+ description = "APIs Test"
+ envgroups = ["test"]
+ }
+ apis-prod = {
+ display_name = "APIs prod"
+ description = "APIs prod"
+ envgroups = ["prod"]
+ iam = {
+ "roles/viewer" = ["group:devops@myorg.com"]
+ }
+ }
+}
+instances = {
+ europe-west1 = {
+ environments = ["europe-west1"]
+ }
+ europe-west3 = {
+ environments = ["europe-west3"]
+ }
+}
+endpoint_attachments = {
+ endpoint-backend-1 = {
+ region = "europe-west1"
+ service_attachment = "projects/my-project-1/serviceAttachments/gkebackend1"
+ }
+ endpoint-backend-2 = {
+ region = "europe-west1"
+ service_attachment = "projects/my-project-2/serviceAttachments/gkebackend2"
+ }
+}
\ No newline at end of file
diff --git a/tests/modules/apigee/all_psc_mode.yaml b/tests/modules/apigee/all_psc_mode.yaml
new file mode 100644
index 00000000..c31c713a
--- /dev/null
+++ b/tests/modules/apigee/all_psc_mode.yaml
@@ -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.
+
+values:
+ google_apigee_endpoint_attachment.endpoint_attachments["endpoint-backend-1"]:
+ endpoint_attachment_id: endpoint-backend-1
+ location: europe-west1
+ service_attachment: projects/my-project-1/serviceAttachments/gkebackend1
+ google_apigee_endpoint_attachment.endpoint_attachments["endpoint-backend-2"]:
+ endpoint_attachment_id: endpoint-backend-2
+ location: europe-west1
+ service_attachment: projects/my-project-2/serviceAttachments/gkebackend2
+ google_apigee_envgroup.envgroups["prod"]:
+ hostnames:
+ - prod.example.com
+ name: prod
+ google_apigee_envgroup.envgroups["test"]:
+ hostnames:
+ - test.example.com
+ name: test
+ google_apigee_envgroup_attachment.envgroup_attachments["apis-prod-prod"]:
+ environment: apis-prod
+ google_apigee_envgroup_attachment.envgroup_attachments["apis-test-test"]:
+ environment: apis-test
+ google_apigee_environment.environments["apis-prod"]:
+ description: APIs prod
+ display_name: APIs prod
+ name: apis-prod
+ google_apigee_environment.environments["apis-test"]:
+ description: APIs Test
+ display_name: APIs test
+ name: apis-test
+ google_apigee_environment_iam_binding.binding["apis-prod-roles/viewer"]:
+ condition: []
+ env_id: apis-prod
+ members:
+ - group:devops@myorg.com
+ role: roles/viewer
+ google_apigee_instance.instances["europe-west3"]:
+ description: Terraform-managed
+ disk_encryption_key_name: null
+ display_name: null
+ location: europe-west3
+ name: instance-europe-west3
+ google_apigee_instance.instances["europe-west1"]:
+ description: Terraform-managed
+ disk_encryption_key_name: null
+ display_name: null
+ location: europe-west1
+ name: instance-europe-west1
+ google_apigee_organization.organization[0]:
+ analytics_region: europe-west1
+ authorized_network: null
+ billing_type: Pay-as-you-go
+ description: null
+ display_name: null
+ project_id: my-project
+ retention: DELETION_RETENTION_UNSPECIFIED
+ runtime_database_encryption_key_name: '123456789'
+ runtime_type: CLOUD
+ disable_vpc_peering: true
+
+counts:
+ google_apigee_endpoint_attachment: 2
+ google_apigee_envgroup: 2
+ google_apigee_envgroup_attachment: 2
+ google_apigee_environment: 2
+ google_apigee_environment_iam_binding: 1
+ google_apigee_instance: 2
+ google_apigee_instance_attachment: 2
+ google_apigee_organization: 1
\ No newline at end of file
diff --git a/tests/modules/apigee/all.tfvars b/tests/modules/apigee/all_vpc_mode.tfvars
similarity index 90%
rename from tests/modules/apigee/all.tfvars
rename to tests/modules/apigee/all_vpc_mode.tfvars
index 69ffb084..03626f76 100644
--- a/tests/modules/apigee/all.tfvars
+++ b/tests/modules/apigee/all_vpc_mode.tfvars
@@ -7,6 +7,7 @@ organization = {
billing_type = "Pay-as-you-go"
database_encryption_key = "123456789"
analytics_region = "europe-west1"
+ disable_vpc_peering = false
}
envgroups = {
test = ["test.example.com"]
@@ -17,13 +18,11 @@ environments = {
display_name = "APIs test"
description = "APIs Test"
envgroups = ["test"]
- regions = ["europe-west1"]
}
apis-prod = {
display_name = "APIs prod"
description = "APIs prod"
envgroups = ["prod"]
- regions = ["europe-west3"]
iam = {
"roles/viewer" = ["group:devops@myorg.com"]
}
@@ -33,10 +32,12 @@ instances = {
europe-west1 = {
runtime_ip_cidr_range = "10.0.4.0/22"
troubleshooting_ip_cidr_range = "10.1.0.0/28"
+ environments = ["apis-test"]
}
europe-west3 = {
runtime_ip_cidr_range = "10.0.6.0/22"
troubleshooting_ip_cidr_range = "10.1.0.16/28"
+ environments = ["apis-prod"]
}
}
endpoint_attachments = {
@@ -48,4 +49,4 @@ endpoint_attachments = {
region = "europe-west1"
service_attachment = "projects/my-project-2/serviceAttachments/gkebackend2"
}
-}
+}
\ No newline at end of file
diff --git a/tests/modules/apigee/all.yaml b/tests/modules/apigee/all_vpc_mode.yaml
similarity index 97%
rename from tests/modules/apigee/all.yaml
rename to tests/modules/apigee/all_vpc_mode.yaml
index c23eab27..2d39429c 100644
--- a/tests/modules/apigee/all.yaml
+++ b/tests/modules/apigee/all_vpc_mode.yaml
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+
values:
google_apigee_endpoint_attachment.endpoint_attachments["endpoint-backend-1"]:
endpoint_attachment_id: endpoint-backend-1
@@ -71,6 +72,7 @@ values:
retention: DELETION_RETENTION_UNSPECIFIED
runtime_database_encryption_key_name: '123456789'
runtime_type: CLOUD
+ disable_vpc_peering: false
counts:
google_apigee_endpoint_attachment: 2
@@ -80,4 +82,4 @@ counts:
google_apigee_environment_iam_binding: 1
google_apigee_instance: 2
google_apigee_instance_attachment: 2
- google_apigee_organization: 1
+ google_apigee_organization: 1
\ No newline at end of file
diff --git a/tests/modules/apigee/examples/minimal-cloud-no-org.yaml b/tests/modules/apigee/examples/minimal-cloud-no-org.yaml
new file mode 100644
index 00000000..eee6638d
--- /dev/null
+++ b/tests/modules/apigee/examples/minimal-cloud-no-org.yaml
@@ -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.
+
+values:
+ module.apigee.google_apigee_envgroup.envgroups["prod"]:
+ hostnames:
+ - prod.example.com
+ name: prod
+ org_id: organizations/project-id
+ module.apigee.google_apigee_envgroup_attachment.envgroup_attachments["apis-prod-prod"]:
+ environment: apis-prod
+ module.apigee.google_apigee_environment.environments["apis-prod"]:
+ description: Terraform-managed
+ display_name: APIs prod
+ name: apis-prod
+ org_id: organizations/project-id
+ module.apigee.google_apigee_instance.instances["europe-west1"]:
+ description: Terraform-managed
+ disk_encryption_key_name: null
+ display_name: null
+ ip_range: ''
+ location: europe-west1
+ name: instance-europe-west1
+ org_id: organizations/project-id
+
+counts:
+ google_apigee_envgroup: 1
+ google_apigee_envgroup_attachment: 1
+ google_apigee_environment: 1
+ google_apigee_instance: 1
diff --git a/tests/modules/apigee/examples/minimal-cloud.yaml b/tests/modules/apigee/examples/minimal-cloud.yaml
new file mode 100644
index 00000000..3a963de6
--- /dev/null
+++ b/tests/modules/apigee/examples/minimal-cloud.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.apigee.google_apigee_envgroup.envgroups["prod"]:
+ hostnames:
+ - prod.example.com
+ name: prod
+ module.apigee.google_apigee_envgroup_attachment.envgroup_attachments["apis-prod-prod"]:
+ environment: apis-prod
+ module.apigee.google_apigee_environment.environments["apis-prod"]:
+ description: APIs Prod
+ display_name: APIs prod
+ name: apis-prod
+ module.apigee.google_apigee_instance.instances["europe-west1"]:
+ description: Terraform-managed
+ disk_encryption_key_name: null
+ display_name: null
+ ip_range: 10.32.0.0/22,10.64.0.0/28
+ location: europe-west1
+ name: instance-europe-west1
+ module.apigee.google_apigee_organization.organization[0]:
+ analytics_region: europe-west1
+ authorized_network: projects/xxx/global/networks/aaa
+ billing_type: PAYG
+ description: null
+ disable_vpc_peering: false
+ display_name: null
+ project_id: project-id
+ retention: DELETION_RETENTION_UNSPECIFIED
+ runtime_database_encryption_key_name: null
+ runtime_type: CLOUD
+
+counts:
+ google_apigee_envgroup: 1
+ google_apigee_envgroup_attachment: 1
+ google_apigee_environment: 1
+ google_apigee_instance: 1
+ google_apigee_organization: 1
diff --git a/tests/modules/apigee/examples/no-peering.yaml b/tests/modules/apigee/examples/no-peering.yaml
new file mode 100644
index 00000000..02c6a5ec
--- /dev/null
+++ b/tests/modules/apigee/examples/no-peering.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.apigee.google_apigee_envgroup.envgroups["prod"]:
+ hostnames:
+ - prod.example.com
+ name: prod
+ module.apigee.google_apigee_envgroup_attachment.envgroup_attachments["apis-prod-prod"]:
+ environment: apis-prod
+ module.apigee.google_apigee_environment.environments["apis-prod"]:
+ description: Terraform-managed
+ display_name: APIs prod
+ name: apis-prod
+ module.apigee.google_apigee_instance.instances["europe-west1"]:
+ description: Terraform-managed
+ disk_encryption_key_name: null
+ display_name: null
+ ip_range: ''
+ location: europe-west1
+ name: instance-europe-west1
+ module.apigee.google_apigee_organization.organization[0]:
+ analytics_region: europe-west1
+ authorized_network: null
+ billing_type: PAYG
+ description: null
+ disable_vpc_peering: true
+ display_name: null
+ project_id: project-id
+ retention: DELETION_RETENTION_UNSPECIFIED
+ runtime_database_encryption_key_name: null
+ runtime_type: CLOUD
+
+counts:
+ google_apigee_envgroup: 1
+ google_apigee_envgroup_attachment: 1
+ google_apigee_environment: 1
+ google_apigee_instance: 1
+ google_apigee_organization: 1
diff --git a/tests/modules/apigee/instance_only_psc_mode.tfvars b/tests/modules/apigee/instance_only_psc_mode.tfvars
new file mode 100644
index 00000000..05fb2cd7
--- /dev/null
+++ b/tests/modules/apigee/instance_only_psc_mode.tfvars
@@ -0,0 +1,13 @@
+project_id = "my-project"
+organization = {
+ display_name = "My Organization"
+ description = "My Organization"
+ runtime_type = "CLOUD"
+ billing_type = "Pay-as-you-go"
+ database_encryption_key = "123456789"
+ analytics_region = "europe-west1"
+ disable_vpc_peering = true
+}
+instances = {
+ europe-west1 = {}
+}
\ No newline at end of file
diff --git a/tests/modules/apigee/instance_only_psc_mode.yaml b/tests/modules/apigee/instance_only_psc_mode.yaml
new file mode 100644
index 00000000..4583d7b4
--- /dev/null
+++ b/tests/modules/apigee/instance_only_psc_mode.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:
+ google_apigee_instance.instances["europe-west1"]:
+ description: Terraform-managed
+ disk_encryption_key_name: null
+ display_name: null
+ location: europe-west1
+ name: instance-europe-west1
+ google_apigee_organization.organization[0]:
+ analytics_region: europe-west1
+ billing_type: Pay-as-you-go
+ description: null
+ display_name: null
+ project_id: my-project
+ retention: DELETION_RETENTION_UNSPECIFIED
+ runtime_database_encryption_key_name: '123456789'
+ runtime_type: CLOUD
+ disable_vpc_peering: true
+
+counts:
+ google_apigee_instance: 1
+ google_apigee_organization: 1
\ No newline at end of file
diff --git a/tests/modules/apigee/instance_only.tfvars b/tests/modules/apigee/instance_only_vpc_mode.tfvars
similarity index 67%
rename from tests/modules/apigee/instance_only.tfvars
rename to tests/modules/apigee/instance_only_vpc_mode.tfvars
index 58074946..2367a884 100644
--- a/tests/modules/apigee/instance_only.tfvars
+++ b/tests/modules/apigee/instance_only_vpc_mode.tfvars
@@ -2,6 +2,6 @@ project_id = "my-project"
instances = {
europe-west1 = {
runtime_ip_cidr_range = "10.0.4.0/22"
- troubleshooting_ip_cidr_range = "10.1.1.0.0/28"
+ troubleshooting_ip_cidr_range = "10.1.1.0/28"
}
-}
+}
\ No newline at end of file
diff --git a/tests/modules/apigee/instance_only.yaml b/tests/modules/apigee/instance_only_vpc_mode.yaml
similarity index 82%
rename from tests/modules/apigee/instance_only.yaml
rename to tests/modules/apigee/instance_only_vpc_mode.yaml
index bc42a370..cf5e7841 100644
--- a/tests/modules/apigee/instance_only.yaml
+++ b/tests/modules/apigee/instance_only_vpc_mode.yaml
@@ -14,7 +14,10 @@
values:
google_apigee_instance.instances["europe-west1"]:
- ip_range: 10.0.4.0/22,10.1.1.0.0/28
+ ip_range: 10.0.4.0/22,10.1.1.0/28
+ description: Terraform-managed
+ disk_encryption_key_name: null
+ display_name: null
location: europe-west1
name: "instance-europe-west1"
org_id: organizations/my-project
diff --git a/tests/modules/apigee/organization_only_psc_mode.tfvars b/tests/modules/apigee/organization_only_psc_mode.tfvars
new file mode 100644
index 00000000..f4808db5
--- /dev/null
+++ b/tests/modules/apigee/organization_only_psc_mode.tfvars
@@ -0,0 +1,10 @@
+project_id = "my-project"
+organization = {
+ display_name = "My Organization"
+ description = "My Organization"
+ runtime_type = "CLOUD"
+ billing_type = "PAYG"
+ database_encryption_key = "123456789"
+ analytics_region = "europe-west1"
+ disable_vpc_peering = true
+}
\ No newline at end of file
diff --git a/tests/modules/apigee/organization_only_psc_mode.yaml b/tests/modules/apigee/organization_only_psc_mode.yaml
new file mode 100644
index 00000000..2bc93b4f
--- /dev/null
+++ b/tests/modules/apigee/organization_only_psc_mode.yaml
@@ -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
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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_apigee_organization.organization[0]:
+ analytics_region: europe-west1
+ authorized_network: null
+ billing_type: PAYG
+ description: null
+ display_name: null
+ project_id: my-project
+ retention: DELETION_RETENTION_UNSPECIFIED
+ runtime_database_encryption_key_name: '123456789'
+ runtime_type: CLOUD
+ disable_vpc_peering: true
+
+counts:
+ google_apigee_organization: 1
diff --git a/tests/modules/apigee/organization_only.tfvars b/tests/modules/apigee/organization_only_vpc_mode.tfvars
similarity index 100%
rename from tests/modules/apigee/organization_only.tfvars
rename to tests/modules/apigee/organization_only_vpc_mode.tfvars
diff --git a/tests/modules/apigee/organization_only.yaml b/tests/modules/apigee/organization_only_vpc_mode.yaml
similarity index 100%
rename from tests/modules/apigee/organization_only.yaml
rename to tests/modules/apigee/organization_only_vpc_mode.yaml
diff --git a/tests/modules/apigee/tftest.yaml b/tests/modules/apigee/tftest.yaml
index f4a9944e..6449de75 100644
--- a/tests/modules/apigee/tftest.yaml
+++ b/tests/modules/apigee/tftest.yaml
@@ -15,13 +15,16 @@
module: modules/apigee
tests:
- all:
+ all_psc_mode:
+ all_vpc_mode:
endpoint_attachment_only:
env_only:
env_only_with_api_proxy_type:
env_only_with_deployment_type:
envgroup_only:
- instance_only:
+ instance_only_psc_mode:
+ instance_only_vpc_mode:
no_instances:
- organization_only:
+ organization_only_psc_mode:
+ organization_only_vpc_mode:
organization_retention:
diff --git a/tests/modules/compute_vm/examples/disk-options.yaml b/tests/modules/compute_vm/examples/disk-options.yaml
index 1a4d58b2..f2f1a053 100644
--- a/tests/modules/compute_vm/examples/disk-options.yaml
+++ b/tests/modules/compute_vm/examples/disk-options.yaml
@@ -29,7 +29,6 @@ values:
- device_name: data1
disk_encryption_key_raw: null
mode: READ_WRITE
- source: test-data1
boot_disk:
- auto_delete: true
disk_encryption_key_raw: null
diff --git a/tests/modules/gke_cluster_autopilot/examples/monitoring-config-kube-state.yaml b/tests/modules/gke_cluster_autopilot/examples/monitoring-config-kube-state.yaml
new file mode 100644
index 00000000..32e5bad5
--- /dev/null
+++ b/tests/modules/gke_cluster_autopilot/examples/monitoring-config-kube-state.yaml
@@ -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
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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:
+ monitoring_config:
+ - enable_components:
+ - DAEMONSET
+ - DEPLOYMENT
+ - HPA
+ - POD
+ - STATEFULSET
+ - STORAGE
+ - SYSTEM_COMPONENTS
+ managed_prometheus:
+ - enabled: true
+
+counts:
+ google_container_cluster: 1
diff --git a/tests/modules/gke_cluster_autopilot/network_tags.tfvars b/tests/modules/gke_cluster_autopilot/network_tags.tfvars
new file mode 100644
index 00000000..4b188f31
--- /dev/null
+++ b/tests/modules/gke_cluster_autopilot/network_tags.tfvars
@@ -0,0 +1,14 @@
+project_id = "my-project"
+location = "europe-west1"
+name = "cluster-1"
+vpc_config = {
+ network = "default"
+ subnetwork = "default"
+}
+tags = [
+ "deep-dark-wood",
+ "hello-gruffalo",
+ "my--precious---nodes",
+ "cluster-1-nodes",
+ "nodes-cluster-1",
+]
diff --git a/tests/modules/gke_cluster_autopilot/network_tags.yaml b/tests/modules/gke_cluster_autopilot/network_tags.yaml
new file mode 100644
index 00000000..5ca48260
--- /dev/null
+++ b/tests/modules/gke_cluster_autopilot/network_tags.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:
+ google_container_cluster.cluster:
+ node_pool_auto_config:
+ - network_tags:
+ - tags:
+ - cluster-1-nodes
+ - deep-dark-wood
+ - hello-gruffalo
+ - my--precious---nodes
+ - nodes-cluster-1
+
+counts:
+ google_container_cluster: 1
diff --git a/tests/modules/gke_cluster_autopilot/tftest.yaml b/tests/modules/gke_cluster_autopilot/tftest.yaml
new file mode 100644
index 00000000..18fc6235
--- /dev/null
+++ b/tests/modules/gke_cluster_autopilot/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: modules/gke-cluster-autopilot
+
+tests:
+ network_tags:
diff --git a/tests/modules/gke_cluster_standard/examples/monitoring-config-control-plane.yaml b/tests/modules/gke_cluster_standard/examples/monitoring-config-control-plane.yaml
new file mode 100644
index 00000000..b3108770
--- /dev/null
+++ b/tests/modules/gke_cluster_standard/examples/monitoring-config-control-plane.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.cluster-1.google_container_cluster.cluster:
+ monitoring_config:
+ - enable_components:
+ - APISERVER
+ - CONTROLLER_MANAGER
+ - SCHEDULER
+ - SYSTEM_COMPONENTS
+ managed_prometheus:
+ - enabled: true
+
+counts:
+ google_container_cluster: 1
diff --git a/tests/modules/gke_cluster_standard/examples/monitoring-config-disable-all.yaml b/tests/modules/gke_cluster_standard/examples/monitoring-config-disable-all.yaml
new file mode 100644
index 00000000..1b5576a4
--- /dev/null
+++ b/tests/modules/gke_cluster_standard/examples/monitoring-config-disable-all.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.google_container_cluster.cluster:
+ monitoring_config:
+ - enable_components: []
+ managed_prometheus:
+ - enabled: false
+
+counts:
+ google_container_cluster: 1
diff --git a/tests/modules/gke_cluster_standard/examples/monitoring-config-kube-state.yaml b/tests/modules/gke_cluster_standard/examples/monitoring-config-kube-state.yaml
new file mode 100644
index 00000000..32e5bad5
--- /dev/null
+++ b/tests/modules/gke_cluster_standard/examples/monitoring-config-kube-state.yaml
@@ -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
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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:
+ monitoring_config:
+ - enable_components:
+ - DAEMONSET
+ - DEPLOYMENT
+ - HPA
+ - POD
+ - STATEFULSET
+ - STORAGE
+ - SYSTEM_COMPONENTS
+ managed_prometheus:
+ - enabled: true
+
+counts:
+ google_container_cluster: 1
diff --git a/tests/modules/kms/examples/basic.yaml b/tests/modules/kms/examples/basic.yaml
index e29297a1..30f40627 100644
--- a/tests/modules/kms/examples/basic.yaml
+++ b/tests/modules/kms/examples/basic.yaml
@@ -18,37 +18,26 @@ values:
name: key-a
purpose: ENCRYPT_DECRYPT
rotation_period: null
- skip_initial_version_creation: null
- timeouts: null
+ skip_initial_version_creation: false
module.kms.google_kms_crypto_key.default["key-b"]:
labels: null
name: key-b
purpose: ENCRYPT_DECRYPT
rotation_period: 604800s
- skip_initial_version_creation: null
- timeouts: null
+ skip_initial_version_creation: false
module.kms.google_kms_crypto_key.default["key-c"]:
labels:
env: test
name: key-c
purpose: ENCRYPT_DECRYPT
rotation_period: null
- skip_initial_version_creation: null
- timeouts: null
- module.kms.google_kms_crypto_key_iam_binding.default["key-a.roles/cloudkms.admin"]:
+ skip_initial_version_creation: false
+ module.kms.google_kms_crypto_key_iam_binding.authoritative["key-a.roles/cloudkms.admin"]:
condition: []
members:
- user:user3@example.com
role: roles/cloudkms.admin
- ? module.kms.google_kms_crypto_key_iam_member.default["key-b.roles/cloudkms.cryptoKeyEncrypterDecrypteruser:user4@example.com"]
- : condition: []
- member: user:user4@example.com
- role: roles/cloudkms.cryptoKeyEncrypterDecrypter
- ? module.kms.google_kms_crypto_key_iam_member.default["key-b.roles/cloudkms.cryptoKeyEncrypterDecrypteruser:user5@example.com"]
- : condition: []
- member: user:user5@example.com
- role: roles/cloudkms.cryptoKeyEncrypterDecrypter
- module.kms.google_kms_crypto_key_iam_member.members["key-b-am1"]:
+ module.kms.google_kms_crypto_key_iam_member.members["key-b-iam1"]:
condition: []
member: user:am1@example.com
role: roles/cloudkms.cryptoKeyEncrypterDecrypter
@@ -56,23 +45,9 @@ values:
location: europe-west1
name: test
project: my-project
- timeouts: null
- module.kms.google_kms_key_ring_iam_member.default["roles/cloudkms.cryptoKeyEncrypterDecrypteruser:user1@example.com"]:
- condition: []
- member: user:user1@example.com
- role: roles/cloudkms.cryptoKeyEncrypterDecrypter
- module.kms.google_kms_key_ring_iam_member.default["roles/cloudkms.cryptoKeyEncrypterDecrypteruser:user2@example.com"]:
- condition: []
- member: user:user2@example.com
- role: roles/cloudkms.cryptoKeyEncrypterDecrypter
counts:
google_kms_crypto_key: 3
google_kms_crypto_key_iam_binding: 1
- google_kms_crypto_key_iam_member: 3
+ google_kms_crypto_key_iam_member: 1
google_kms_key_ring: 1
- google_kms_key_ring_iam_member: 2
- modules: 1
- resources: 10
-
-outputs: {}
diff --git a/tests/modules/kms/examples/purpose.yaml b/tests/modules/kms/examples/purpose.yaml
index c08779b2..9f97ad52 100644
--- a/tests/modules/kms/examples/purpose.yaml
+++ b/tests/modules/kms/examples/purpose.yaml
@@ -15,25 +15,19 @@
values:
module.kms.google_kms_crypto_key.default["key-a"]:
name: key-a
- purpose: ENCRYPT_DECRYPT
- module.kms.google_kms_crypto_key.default["key-b"]:
- name: key-b
- purpose: ENCRYPT_DECRYPT
- module.kms.google_kms_crypto_key.default["key-c"]:
- name: key-c
purpose: ASYMMETRIC_SIGN
version_template:
- algorithm: EC_SIGN_P384_SHA384
- protection_level: SOFTWARE
+ protection_level: HSM
module.kms.google_kms_key_ring.default[0]:
location: europe-west1
name: test
project: my-project
counts:
- google_kms_crypto_key: 3
+ google_kms_crypto_key: 1
google_kms_key_ring: 1
modules: 1
- resources: 4
+ resources: 2
outputs: {}
diff --git a/tests/modules/net_vpc/examples/factory.yaml b/tests/modules/net_vpc/examples/factory.yaml
index fb348397..50aa01e1 100644
--- a/tests/modules/net_vpc/examples/factory.yaml
+++ b/tests/modules/net_vpc/examples/factory.yaml
@@ -48,8 +48,7 @@ values:
tags: null
timeouts: null
module.vpc.google_compute_subnetwork.proxy_only["europe-west4/subnet-proxy"]:
- description: Terraform-managed proxy-only subnet for Regional HTTPS or Internal
- HTTPS LB.
+ description: Terraform-managed proxy-only subnet for Regional HTTPS, Internal HTTPS or Cross-Regional HTTPS Internal LB.
ip_cidr_range: 10.1.0.0/24
ipv6_access_type: null
log_config: []
@@ -59,6 +58,17 @@ values:
region: europe-west4
role: ACTIVE
timeouts: null
+ module.vpc.google_compute_subnetwork.proxy_only["australia-southeast2/subnet-proxy-global"]:
+ description: Terraform-managed proxy-only subnet for Regional HTTPS, Internal HTTPS or Cross-Regional HTTPS Internal LB.
+ ip_cidr_range: 10.4.0.0/24
+ ipv6_access_type: null
+ log_config: []
+ name: subnet-proxy-global
+ project: my-project
+ purpose: GLOBAL_MANAGED_PROXY
+ region: australia-southeast2
+ role: ACTIVE
+ timeouts: null
module.vpc.google_compute_subnetwork.psc["europe-west4/subnet-psc"]:
description: Terraform-managed subnet for Private Service Connect (PSC NAT).
ip_cidr_range: 10.2.0.0/24
@@ -127,9 +137,9 @@ values:
counts:
google_compute_network: 1
google_compute_route: 2
- google_compute_subnetwork: 5
+ google_compute_subnetwork: 6
google_compute_subnetwork_iam_binding: 1
modules: 1
- resources: 9
+ resources: 10
outputs: {}
diff --git a/tests/modules/net_vpc/examples/proxy-only-subnets.yaml b/tests/modules/net_vpc/examples/proxy-only-subnets.yaml
index 6e2069aa..cf32912d 100644
--- a/tests/modules/net_vpc/examples/proxy-only-subnets.yaml
+++ b/tests/modules/net_vpc/examples/proxy-only-subnets.yaml
@@ -17,7 +17,7 @@ values:
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.
+ description: Terraform-managed proxy-only subnet for Regional HTTPS, Internal HTTPS or Cross-Regional HTTPS Internal LB.
ip_cidr_range: 10.0.1.0/24
log_config: []
name: regional-proxy
@@ -25,6 +25,15 @@ values:
purpose: REGIONAL_MANAGED_PROXY
region: europe-west1
role: ACTIVE
+ module.vpc.google_compute_subnetwork.proxy_only["australia-southeast2/global-proxy"]:
+ description: Terraform-managed proxy-only subnet for Regional HTTPS, Internal HTTPS or Cross-Regional HTTPS Internal LB.
+ ip_cidr_range: 10.0.4.0/24
+ log_config: []
+ name: global-proxy
+ project: my-project
+ purpose: GLOBAL_MANAGED_PROXY
+ region: australia-southeast2
+ 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
@@ -37,4 +46,4 @@ values:
counts:
google_compute_network: 1
- google_compute_subnetwork: 2
+ google_compute_subnetwork: 3
diff --git a/tests/modules/net_vpc/examples/subnet-iam.yaml b/tests/modules/net_vpc/examples/subnet-iam.yaml
index 68b03418..1b925f48 100644
--- a/tests/modules/net_vpc/examples/subnet-iam.yaml
+++ b/tests/modules/net_vpc/examples/subnet-iam.yaml
@@ -80,7 +80,7 @@ values:
region: europe-west1
role: roles/compute.networkUser
subnetwork: subnet-1
- module.vpc.google_compute_subnetwork_iam_binding.bindings["europe-west1/subnet-1.roles/compute.networkUser.test_condition"]:
+ module.vpc.google_compute_subnetwork_iam_binding.bindings["subnet-1-iam"]:
condition:
- description: null
expression: resource.matchTag('123456789012/env', 'prod')
@@ -91,7 +91,7 @@ values:
region: europe-west1
role: roles/compute.networkUser
subnetwork: subnet-1
- module.vpc.google_compute_subnetwork_iam_member.bindings["subnet-2-am1"]:
+ module.vpc.google_compute_subnetwork_iam_member.bindings["subnet-2-iam"]:
condition: []
member: user:am1@example.com
project: my-project
diff --git a/tests/modules/project/examples/iam-bindings.yaml b/tests/modules/project/examples/iam-bindings.yaml
index f1f09e36..c9fee925 100644
--- a/tests/modules/project/examples/iam-bindings.yaml
+++ b/tests/modules/project/examples/iam-bindings.yaml
@@ -23,7 +23,7 @@ values:
project_id: foo-project-example
skip_delete: false
timeouts: null
- module.project.google_project_iam_binding.bindings["roles/resourcemanager.projectIamAdmin"]:
+ module.project.google_project_iam_binding.bindings["iam_admin_conditional"]:
condition:
- description: null
expression: "api.getAttribute(\n 'iam.googleapis.com/modifiedGrantsByRole',\
@@ -54,4 +54,3 @@ counts:
resources: 4
outputs: {}
-
diff --git a/tests/modules/pubsub/examples/simple.yaml b/tests/modules/pubsub/examples/simple.yaml
index 51094a51..6fe54ec6 100644
--- a/tests/modules/pubsub/examples/simple.yaml
+++ b/tests/modules/pubsub/examples/simple.yaml
@@ -16,14 +16,14 @@ values:
module.pubsub.google_pubsub_topic.default:
name: my-topic
project: my-project
- module.pubsub.google_pubsub_topic_iam_binding.default["roles/pubsub.subscriber"]:
+ module.pubsub.google_pubsub_topic_iam_binding.authoritative["roles/pubsub.subscriber"]:
condition: []
members:
- user:user1@example.com
project: my-project
role: roles/pubsub.subscriber
topic: my-topic
- module.pubsub.google_pubsub_topic_iam_binding.default["roles/pubsub.viewer"]:
+ module.pubsub.google_pubsub_topic_iam_binding.authoritative["roles/pubsub.viewer"]:
condition: []
members:
- group:foo@example.com
diff --git a/tests/modules/pubsub/examples/subscription-iam.yaml b/tests/modules/pubsub/examples/subscription-iam.yaml
index d0fa9fb6..42ed2565 100644
--- a/tests/modules/pubsub/examples/subscription-iam.yaml
+++ b/tests/modules/pubsub/examples/subscription-iam.yaml
@@ -13,10 +13,10 @@
# limitations under the License.
values:
- module.pubsub.google_pubsub_subscription_iam_binding.default["test-1.roles/pubsub.subscriber"]:
+ module.pubsub.google_pubsub_subscription_iam_binding.authoritative["test-1.roles/pubsub.subscriber"]:
condition: []
members:
- - user:user1@ludomagno.net
+ - user:user1@example.com
project: my-project
role: roles/pubsub.subscriber
subscription: test-1
diff --git a/tests/modules/pubsub/examples/subscriptions.yaml b/tests/modules/pubsub/examples/subscriptions.yaml
index a87a6d47..b1a94212 100644
--- a/tests/modules/pubsub/examples/subscriptions.yaml
+++ b/tests/modules/pubsub/examples/subscriptions.yaml
@@ -16,22 +16,22 @@ values:
module.pubsub.google_pubsub_subscription.default["test-pull"]:
bigquery_config: []
dead_letter_policy: []
- enable_exactly_once_delivery: null
- enable_message_ordering: null
+ enable_exactly_once_delivery: False
+ enable_message_ordering: False
filter: null
labels: null
message_retention_duration: 604800s
name: test-pull
project: my-project
push_config: []
- retain_acked_messages: null
+ retain_acked_messages: False
retry_policy: []
topic: my-topic
module.pubsub.google_pubsub_subscription.default["test-pull-override"]:
bigquery_config: []
dead_letter_policy: []
- enable_exactly_once_delivery: null
- enable_message_ordering: null
+ enable_exactly_once_delivery: False
+ enable_message_ordering: False
filter: null
labels:
test: override
@@ -39,7 +39,7 @@ values:
name: test-pull-override
project: my-project
push_config: []
- retain_acked_messages: true
+ retain_acked_messages: True
retry_policy: []
topic: my-topic
module.pubsub.google_pubsub_topic.default: