Compare commits

...

91 Commits

Author SHA1 Message Date
Julio Castillo d36c53bbae
Update README.md 2023-08-24 11:29:03 +02:00
Julio Castillo d051d41697
Merge pull request #1614 from richard-olson/richard/nfw-policy-factory-fix
Fix net-firewall-policy factory name and action
2023-08-23 16:05:59 +02:00
Julio Castillo 96fce9c0a4
Merge branch 'master' into richard/nfw-policy-factory-fix 2023-08-23 14:28:58 +02:00
Luca Prete 50a449965f
Fix: align stage-2-e-nva-bgp to the latest APIs 2023-08-23 13:34:11 +02:00
Richard Olson 2f4b141d7a switch default behaviour for ingress 2023-08-23 21:17:47 +10:00
Richard Olson def012d32e customisable rule action 2023-08-23 21:08:23 +10:00
Richard Olson 18af929331 add name to factory rules 2023-08-23 20:58:07 +10:00
Julio Castillo 927c04a6d4
Merge pull request #1584 from ehorning/ehorning/support-gcs-object-upload
add support for object upload to gcs module
2023-08-22 19:01:18 +02:00
Erin Horning 4885b8a7f9 objects non-nullable 2023-08-22 09:40:50 -06:00
Erin Horning 9a0f9f607f objects output 2023-08-22 09:35:39 -06:00
Erin Horning 2f3d29395f remove validation 2023-08-22 09:09:37 -06:00
Ludovico Magnocavallo 95a0c76b18
Merge branch 'master' into ehorning/support-gcs-object-upload 2023-08-22 09:48:32 +02:00
Ludovico Magnocavallo ff8eef6a6f
use cloud run bindings for cf v2 invoker role, refactor iam handling in cf v2 and cloud run (#1609) 2023-08-22 07:23:49 +00:00
Luca Prete 8ca60881f1
Fix: use existing variable to optionally name fw policies (#1610) 2023-08-22 08:55:56 +02:00
Erin Horning 33d51dbee4
Merge branch 'master' into ehorning/support-gcs-object-upload 2023-08-21 16:36:35 -06:00
Erin Horning 6f1f6f5085 add additional parameters 2023-08-21 16:35:47 -06:00
Ludovico Magnocavallo 91f71fef68
trap requests timeout error (#1607) 2023-08-21 18:37:54 +02:00
Julio Castillo 46af8b7956
Merge pull request #1590 from GoogleCloudPlatform/elia-gcve
GCVE module first release
2023-08-21 09:05:44 +02:00
Julio Castillo 49a4550b5d
Merge branch 'master' into elia-gcve 2023-08-21 08:44:37 +02:00
Ludovico Magnocavallo 0d17af7967
Update 20230816-iam-refactor.md 2023-08-21 07:11:24 +02:00
Ludovico Magnocavallo e7eeed12f8
Update 20230816-iam-refactor.md 2023-08-21 07:10:29 +02:00
Ludovico Magnocavallo 63b0480499
Update 20230816-iam-refactor.md 2023-08-21 07:09:54 +02:00
Ludovico Magnocavallo e43be5b387
Update README.md 2023-08-21 07:01:06 +02:00
Ludo 1c1446f4c2
update changelog 2023-08-20 18:37:31 +02:00
Ludovico Magnocavallo 5cb4accbd0
Merge branch 'master' into elia-gcve 2023-08-20 10:19:14 +02:00
Ludovico Magnocavallo 819894d2ba
IAM interface refactor (#1595)
* IAM modules refactor proposal

* policy

* subheading

* Update 20230816-iam-refactor.md

* log Julio's +1

* data-catalog-policy-tag

* dataproc

* dataproc

* folder

* folder

* folder

* folder

* project

* better filtering in test examples

* project

* folder

* folder

* organization

* fix variable descriptions

* kms

* net-vpc

* dataplex-datascan

* modules/iam-service-account

* modules/source-repository/

* blueprints/cloud-operations/vm-migration/

* blueprints/third-party-solutions/wordpress

* dataplex-datascan

* blueprints/cloud-operations/workload-identity-federation

* blueprints/data-solutions/cloudsql-multiregion/

* blueprints/data-solutions/composer-2

* Update 20230816-iam-refactor.md

* Update 20230816-iam-refactor.md

* capture discussion in architectural doc

* update variable names and refactor proposal

* project

* blueprints first round

* folder

* organization

* data-catalog-policy-tag

* re-enable folder inventory

* project module style fix

* dataproc

* source-repository

* source-repository tests

* dataplex-datascan

* dataplex-datascan tests

* net-vpc

* net-vpc test examples

* iam-service-account

* iam-service-account test examples

* kms

* boilerplate

* tfdoc

* fix module tests

* more blueprint fixes

* fix typo in data blueprints

* incomplete refactor of data platform foundations

* tfdoc

* data platform foundation

* refactor data platform foundation iam locals

* remove redundant example test

* shielded folder fix

* fix typo

* project factory

* project factory outputs

* tfdoc

* test workflow: less verbose tests, fix tf version

* re-enable -vv, shorter traceback, fix action version

* ignore github extension warning, re-enable action version

* fast bootstrap IAM, untested

* bootstrap stage IAM fixes

* stage 0 tests

* fast stage 1

* tenant stage 1

* minor changes to fast stage 0 and 1

* fast security stage

* fast mt stage 0

* fast mt stage 0

* fast pf
2023-08-20 09:44:20 +02:00
Ludovico Magnocavallo 3a8071d93c
Merge branch 'master' into elia-gcve 2023-08-20 08:18:53 +02:00
lcaggio 6eeba5e599
[Data Platform] Update README.md (#1601)
Fix hardcoded path in readme.
2023-08-18 18:27:43 +02:00
eliamaldini 126d75a311 fixed typo 2023-08-18 17:01:12 +02:00
eliamaldini 103443dc30 fixed typo 2023-08-18 16:57:35 +02:00
eliamaldini 1b93197b87 fixed typo 2023-08-18 16:56:21 +02:00
eliamaldini c4ada40275 fixed typo 2023-08-18 16:52:58 +02:00
Julio Castillo cb3ff80745
Merge pull request #1600 from LiuVII/LiuVII/cloud-run-fix
fix(cloud-run): move cpu boost annotation to revision
2023-08-18 16:46:25 +02:00
eliamaldini bf26580b9d fixed variables order 2023-08-18 15:52:56 +02:00
eliamaldini 9452a14ac7 output cleanup 2023-08-18 15:49:20 +02:00
eliamaldini 81b567684b outputs cleaup 2023-08-18 15:41:06 +02:00
eliamaldini 87e82244af fixed tests 2023-08-18 15:38:35 +02:00
eliamaldini e762e93677 fixed variable names 2023-08-18 15:37:32 +02:00
Michael e1e3826f6d
fix(cloud-run): move cpu boost annotation to revision 2023-08-18 13:53:00 +01:00
eliamaldini d568408331 fixed variable name and regex 2023-08-18 11:20:27 +02:00
eliamaldini 5a298780c1 changed variable name 2023-08-18 11:19:35 +02:00
Julio Castillo 145a04827a
Merge pull request #1599 from bluPhy/master
Fixing some typos
2023-08-18 10:29:25 +02:00
Julio Castillo 476d6eaf30
Update variables.tf 2023-08-18 10:07:23 +02:00
Julio Castillo dc78ad3493
Update outputs.tf 2023-08-18 10:06:32 +02:00
Alejandro Leal c2c7ec4f1d Adding exception for idx 2023-08-18 06:35:09 +00:00
Alejandro Leal ea0de3adbb Fixing some typos 2023-08-18 05:51:00 +00:00
Julio Castillo f173a9cf25
Merge pull request #1598 from merit/startup-cpu-boost
feat(cloud-run): add startup cpu boost option
2023-08-18 00:05:23 +02:00
Jay Schwerberg 574c7548d8
feat(cloud-run): add startup cpu boost option 2023-08-17 14:43:27 -07:00
eliamaldini 6a5739bf91 gcve net requirements link 2023-08-17 12:25:33 +02:00
eliamaldini c6f5d47c66 fixed variable name 2023-08-17 12:19:47 +02:00
eliamaldini 739bbf1aef fixed variable name 2023-08-17 12:11:42 +02:00
eliamaldini 3feaad0c1c fixed variable name 2023-08-17 12:10:44 +02:00
eliamaldini 0e2ee8bceb fixed typo 2023-08-17 12:05:44 +02:00
eliamaldini 8e9d544be6 fixed description 2023-08-17 12:04:48 +02:00
Stefan Moser dcb3c32761
fix null object exception in bootstrap output when using cloudsource repos (#1597) 2023-08-17 09:03:23 +00:00
eliamaldini 1e54ddd710 Merge branch 'elia-gcve' of github.com:GoogleCloudPlatform/cloud-foundation-fabric into elia-gcve 2023-08-17 09:39:50 +02:00
eliamaldini e927bf3858 Fixed typo 2023-08-17 09:37:48 +02:00
eliamaldini 77d80a40c3 added link to GCVE module 2023-08-17 09:34:18 +02:00
eliamaldini a7fd3e2616 fixed file name 2023-08-17 09:25:44 +02:00
Ludovico Magnocavallo def2f476d1
Add support for conditions to `iam_members` module variables (#1594)
* project

* data-catalog-policy-tag

* dataproc

* folder

* iam-service-account

* kms

* net-vpc

* organization

* source-repository

* dataplex-datascan
2023-08-15 16:28:23 +02:00
Ludo 1644ab8279
update changelog 2023-08-15 15:19:29 +02:00
Ludovico Magnocavallo 2423fd40c1
Fix FAST CI/CD for Gitlab (#1593)
* fix cicd (multitenant untested)

* tfdoc

* rename allowed_audiences to audiences, align multitenant
2023-08-15 12:59:31 +02:00
Ludovico Magnocavallo acc191c58e
Merge branch 'master' into elia-gcve 2023-08-15 09:41:00 +02:00
eliamaldini 6fee2a9eb3 Merge branch 'elia-gcve' of github.com:GoogleCloudPlatform/cloud-foundation-fabric into elia-gcve 2023-08-14 14:46:16 +02:00
eliamaldini f3d5dd8e34 Added link to GCVE module 2023-08-14 14:41:33 +02:00
erabusi b6b660f4f3
feat: 🎸 (modules/cloudsql-instance):add project_id for ssl cert (#1591) 2023-08-14 12:40:25 +02:00
Ludo 4c0c9b16f4
update changelog 2023-08-14 12:07:38 +02:00
Ludovico Magnocavallo 5689aacac2
Merge branch 'master' into elia-gcve 2023-08-14 11:56:47 +02:00
Ludovico Magnocavallo adf2621727
Add new `iam_members` variable to IAM additive module interfaces (#1589)
* resource management modules

* data catalog policy

* dataproc

* service account

* kms

* net-vpc

* source repository

* dataplex datascan

* service account module variable order
2023-08-14 09:54:50 +00:00
eliamaldini a509756f1b GCVE module first release 2023-08-14 11:48:27 +02:00
erabusi f9509ad6b7
feat: 🎸 (modules/cloudsql-instance): enable require_ssl cert support (#1588) 2023-08-14 11:37:03 +02:00
Ludo ec56a86bbd
update changelog 2023-08-14 07:54:08 +02:00
Ludovico Magnocavallo 841459a059
Fix factory rules key in net firewall policy module (#1587)
* fix factory rules key in net firewall policy

* fix test
2023-08-14 07:52:36 +02:00
Erin Horning 10faf82b8d linting 2023-08-11 11:20:06 -06:00
Erin Horning 2fa6369ba0 Merge branch 'master' of https://github.com/GoogleCloudPlatform/cloud-foundation-fabric into ehorning/support-gcs-object-upload 2023-08-11 11:19:21 -06:00
Ludo ad1d5a7ddc
update changelog 2023-08-11 17:33:46 +02:00
Sam Bentley b1679ad21a
Fix: Instance level stateful disk config (#1578)
* update doco

* fix bug in TF code

* change instance name in README to fix test

* revert disk name

* Update stateful.yaml

* fix examples and tests

---------

Co-authored-by: Julio Castillo <juliocc@gmail.com>
Co-authored-by: Ludovico Magnocavallo <ludomagno@google.com>
2023-08-11 15:25:17 +00:00
Ludo 5a12e2a773
tag latest release in changelog 2023-08-11 17:06:58 +02:00
Julio Castillo c02e2fc048
Merge pull request #1585 from GoogleCloudPlatform/jccb/test-print-failed-inventory
Print inventory path when a test fails
2023-08-11 12:28:08 +02:00
Ludovico Magnocavallo f2e26b6a2c
Merge branch 'master' into jccb/test-print-failed-inventory 2023-08-11 12:10:31 +02:00
Julio Castillo 4d6574e0a1 Print inventory path when a test fails 2023-08-11 12:07:07 +02:00
Erin Horning df1fded669 add support for object upload to gcs module 2023-08-10 21:43:53 -06:00
Ludovico Magnocavallo c5a77ebfe3
fix module path for teams cicd (#1583) 2023-08-09 21:41:56 +00:00
Mikhail Filipchuk 78095063f7
feat(modules/cloud-run): add gen2 exec env support (#1582) 2023-08-09 23:04:16 +02:00
Ludo 30774edd1f
update changelog 2023-08-09 15:09:16 +02:00
Matt 9600047a32
Enable team CI/CD impersonation (#1579) 2023-08-09 08:46:24 -04:00
Ludovico Magnocavallo 79373721df
Remove firewall policy management from resource management modules (#1581)
* rename firewall policy module, fix outputs

* add TOC to firewall policy module

* don't depend policy on parent id

* remove firewall policy from resource management modules

* remove factory conditionals

* fast net a and b

* fast stages

* fast tfdoc

* fast tfdoc

* remove unused test

* fix shielded folder blueprint

* fix shielded folder blueprint
2023-08-09 11:23:07 +00:00
apichick b7ff8f0933
Merge pull request #1580 from apichick/apigee-addons
Apigee addons
2023-08-09 08:33:20 +02:00
Miren Esnaola 6a13742e3c Apigee addons 2023-08-09 08:12:06 +02:00
Ludovico Magnocavallo 80ada0e8dd
Refactor firewall policy module (#1576)
* refactor module interface

* hierarchical attachment and example

* hierarchical rules and TODO

* split rules resources

* additional fields

* keep using a single resource for rules

* factory

* factory test

* boilerplate

* Prefix ingress and egress rule ids

* Tests for other firewall policy types

* Fix rule id and names

---------

Co-authored-by: Julio Castillo <jccb@google.com>
2023-08-08 16:57:59 +00:00
Ludo 8917333bde
update changelog 2023-08-08 08:11:07 +02:00
282 changed files with 7648 additions and 5332 deletions

View File

@ -28,7 +28,7 @@ jobs:
name: "Create tag on master if there was activity in last 24 hours" name: "Create tag on master if there was activity in last 24 hours"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: "Check changes and tag" - name: "Check changes and tag"
run: | run: |

View File

@ -17,9 +17,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
tags:
- ci
- lint
jobs: jobs:
linting: linting:

View File

@ -19,9 +19,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
tags:
- ci
- test
env: env:
GOOGLE_APPLICATION_CREDENTIALS: "/home/runner/credentials.json" GOOGLE_APPLICATION_CREDENTIALS: "/home/runner/credentials.json"
@ -39,7 +36,7 @@ jobs:
- uses: hashicorp/setup-terraform@v2 - uses: hashicorp/setup-terraform@v2
with: with:
terraform_version: ${{ env.TERRAFORM_VERSION }} terraform_version: ${{ env.TF_VERSION }}
terraform_wrapper: false terraform_wrapper: false
- name: Build lockfile and fetch providers - name: Build lockfile and fetch providers
@ -76,10 +73,10 @@ jobs:
uses: ./.github/actions/fabric-tests uses: ./.github/actions/fabric-tests
with: with:
PYTHON_VERSION: ${{ env.PYTHON_VERSION }} PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }} TERRAFORM_VERSION: ${{ env.TF_VERSION }}
- name: Run tests on documentation examples - name: Run tests on documentation examples
run: pytest -vv -n4 -k blueprints/ tests/examples run: pytest -vv -n4 --tb=line -k blueprints/ tests/examples
examples-modules: examples-modules:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -91,10 +88,10 @@ jobs:
uses: ./.github/actions/fabric-tests uses: ./.github/actions/fabric-tests
with: with:
PYTHON_VERSION: ${{ env.PYTHON_VERSION }} PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }} TERRAFORM_VERSION: ${{ env.TF_VERSION }}
- name: Run tests on documentation examples - name: Run tests on documentation examples
run: pytest -vv -n4 -k modules/ tests/examples run: pytest -vv -n4 --tb=line -k modules/ tests/examples
blueprints: blueprints:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -106,10 +103,10 @@ jobs:
uses: ./.github/actions/fabric-tests uses: ./.github/actions/fabric-tests
with: with:
PYTHON_VERSION: ${{ env.PYTHON_VERSION }} PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }} TERRAFORM_VERSION: ${{ env.TF_VERSION }}
- name: Run tests environments - name: Run tests environments
run: pytest -vv -n4 tests/blueprints run: pytest -vv -n4 --tb=line tests/blueprints
modules: modules:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -121,10 +118,10 @@ jobs:
uses: ./.github/actions/fabric-tests uses: ./.github/actions/fabric-tests
with: with:
PYTHON_VERSION: ${{ env.PYTHON_VERSION }} PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }} TERRAFORM_VERSION: ${{ env.TF_VERSION }}
- name: Run tests modules - name: Run tests modules
run: pytest -vv -n4 tests/modules run: pytest -vv -n4 --tb=line tests/modules
fast: fast:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -136,7 +133,7 @@ jobs:
uses: ./.github/actions/fabric-tests uses: ./.github/actions/fabric-tests
with: with:
PYTHON_VERSION: ${{ env.PYTHON_VERSION }} PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }} TERRAFORM_VERSION: ${{ env.TF_VERSION }}
- name: Run tests on FAST stages - name: Run tests on FAST stages
run: pytest -vv -n4 tests/fast run: pytest -vv -n4 --tb=line tests/fast

1
.gitignore vendored
View File

@ -10,6 +10,7 @@
**/.test.lock **/.test.lock
.idea .idea
.vscode .vscode
.idx/dev.nix
backend.tf backend.tf
backend-config.hcl backend-config.hcl
credentials.json credentials.json

View File

@ -4,10 +4,49 @@ All notable changes to this project will be documented in this file.
<!-- markdownlint-disable MD024 --> <!-- markdownlint-disable MD024 -->
## [Unreleased] ## [Unreleased]
<!-- None < 2023-07-07 16:22:14+00:00 --> <!-- None < 2023-08-09 17:02:13+00:00 -->
### BLUEPRINTS ### BLUEPRINTS
- [[#1595](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1595)] **incompatible change:** IAM interface refactor ([ludoo](https://github.com/ludoo)) <!-- 2023-08-20 07:44:20+00:00 -->
- [[#1601](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1601)] [Data Platform] Update README.md ([lcaggio](https://github.com/lcaggio)) <!-- 2023-08-18 16:27:43+00:00 -->
### DOCUMENTATION
- [[#1595](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1595)] **incompatible change:** IAM interface refactor ([ludoo](https://github.com/ludoo)) <!-- 2023-08-20 07:44:20+00:00 -->
### FAST
- [[#1595](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1595)] **incompatible change:** IAM interface refactor ([ludoo](https://github.com/ludoo)) <!-- 2023-08-20 07:44:20+00:00 -->
- [[#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)) <!-- 2023-08-17 09:03:23+00:00 -->
- [[#1593](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1593)] Fix FAST CI/CD for Gitlab ([ludoo](https://github.com/ludoo)) <!-- 2023-08-15 10:59:31+00:00 -->
- [[#1583](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1583)] Fix module path for teams cicd ([ludoo](https://github.com/ludoo)) <!-- 2023-08-09 21:41:57+00:00 -->
### MODULES
- [[#1595](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1595)] **incompatible change:** IAM interface refactor ([ludoo](https://github.com/ludoo)) <!-- 2023-08-20 07:44:20+00:00 -->
- [[#1600](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1600)] fix(cloud-run): move cpu boost annotation to revision ([LiuVII](https://github.com/LiuVII)) <!-- 2023-08-18 14:46:25+00:00 -->
- [[#1599](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1599)] Fixing some typos ([bluPhy](https://github.com/bluPhy)) <!-- 2023-08-18 08:29:26+00:00 -->
- [[#1598](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1598)] feat(cloud-run): add startup cpu boost option ([JSchwerberg](https://github.com/JSchwerberg)) <!-- 2023-08-17 22:05:24+00:00 -->
- [[#1594](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1594)] Add support for conditions to `iam_members` module variables ([ludoo](https://github.com/ludoo)) <!-- 2023-08-15 14:28:23+00:00 -->
- [[#1591](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1591)] feat: 🎸 (modules/cloudsql-instance):add project_id for ssl cert ([erabusi](https://github.com/erabusi)) <!-- 2023-08-14 10:40:25+00:00 -->
- [[#1589](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1589)] Add new `iam_members` variable to IAM additive module interfaces ([ludoo](https://github.com/ludoo)) <!-- 2023-08-14 09:54:50+00:00 -->
- [[#1588](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1588)] feat: 🎸 (modules/cloudsql-instance): enable require_ssl cert support ([erabusi](https://github.com/erabusi)) <!-- 2023-08-14 09:37:04+00:00 -->
- [[#1587](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1587)] **incompatible change:** Fix factory rules key in net firewall policy module ([ludoo](https://github.com/ludoo)) <!-- 2023-08-14 05:52:37+00:00 -->
- [[#1578](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1578)] Fix: Instance level stateful disk config ([beardedsamwise](https://github.com/beardedsamwise)) <!-- 2023-08-11 15:25:17+00:00 -->
- [[#1582](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1582)] feat(modules/cloud-run): add gen2 exec env support ([LiuVII](https://github.com/LiuVII)) <!-- 2023-08-09 21:04:17+00:00 -->
### TOOLS
- [[#1595](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1595)] **incompatible change:** IAM interface refactor ([ludoo](https://github.com/ludoo)) <!-- 2023-08-20 07:44:20+00:00 -->
- [[#1585](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1585)] Print inventory path when a test fails ([juliocc](https://github.com/juliocc)) <!-- 2023-08-11 10:28:08+00:00 -->
## [25.0.0] - 2023-08-09
<!-- 2023-08-09 17:02:13+00:00 < 2023-07-07 16:22:14+00:00 -->
### BLUEPRINTS
- [[#1581](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1581)] **incompatible change:** Remove firewall policy management from resource management modules ([ludoo](https://github.com/ludoo)) <!-- 2023-08-09 11:23:08+00:00 -->
- [[#1573](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1573)] Add information about required groups ([wiktorn](https://github.com/wiktorn)) <!-- 2023-08-06 18:27:59+00:00 --> - [[#1573](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1573)] Add information about required groups ([wiktorn](https://github.com/wiktorn)) <!-- 2023-08-06 18:27:59+00:00 -->
- [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) <!-- 2023-08-06 09:25:45+00:00 --> - [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) <!-- 2023-08-06 09:25:45+00:00 -->
- [[#1560](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1560)] Removed unused attribute in variable of ha-vpn-over-blueprint blueprint ([apichick](https://github.com/apichick)) <!-- 2023-08-02 11:41:08+00:00 --> - [[#1560](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1560)] Removed unused attribute in variable of ha-vpn-over-blueprint blueprint ([apichick](https://github.com/apichick)) <!-- 2023-08-02 11:41:08+00:00 -->
@ -23,6 +62,7 @@ All notable changes to this project will be documented in this file.
### DOCUMENTATION ### DOCUMENTATION
- [[#1581](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1581)] **incompatible change:** Remove firewall policy management from resource management modules ([ludoo](https://github.com/ludoo)) <!-- 2023-08-09 11:23:08+00:00 -->
- [[#1573](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1573)] Add information about required groups ([wiktorn](https://github.com/wiktorn)) <!-- 2023-08-06 18:27:59+00:00 --> - [[#1573](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1573)] Add information about required groups ([wiktorn](https://github.com/wiktorn)) <!-- 2023-08-06 18:27:59+00:00 -->
- [[#1545](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1545)] add dataplex autodq base module ([thinhha](https://github.com/thinhha)) <!-- 2023-08-02 11:16:33+00:00 --> - [[#1545](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1545)] add dataplex autodq base module ([thinhha](https://github.com/thinhha)) <!-- 2023-08-02 11:16:33+00:00 -->
- [[#1557](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1557)] renaming net-vpc-swp to net-swp ([skalolazka](https://github.com/skalolazka)) <!-- 2023-08-01 15:48:22+00:00 --> - [[#1557](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1557)] renaming net-vpc-swp to net-swp ([skalolazka](https://github.com/skalolazka)) <!-- 2023-08-01 15:48:22+00:00 -->
@ -33,6 +73,8 @@ All notable changes to this project will be documented in this file.
### FAST ### FAST
- [[#1579](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1579)] Enable team CI/CD impersonation ([williamsmt](https://github.com/williamsmt)) <!-- 2023-08-09 12:46:24+00:00 -->
- [[#1581](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1581)] **incompatible change:** Remove firewall policy management from resource management modules ([ludoo](https://github.com/ludoo)) <!-- 2023-08-09 11:23:08+00:00 -->
- [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) <!-- 2023-08-06 09:25:45+00:00 --> - [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) <!-- 2023-08-06 09:25:45+00:00 -->
- [[#1566](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1566)] Remove unused ASN numbers from CloudNAT to avoid provider errors ([LucaPrete](https://github.com/LucaPrete)) <!-- 2023-08-04 08:02:12+00:00 --> - [[#1566](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1566)] Remove unused ASN numbers from CloudNAT to avoid provider errors ([LucaPrete](https://github.com/LucaPrete)) <!-- 2023-08-04 08:02:12+00:00 -->
- [[#1563](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1563)] Update FAST CI/CD workflows so it can work with ID_TOKEN and Gitlab 15+ ([LucaPrete](https://github.com/LucaPrete)) <!-- 2023-08-03 16:09:45+00:00 --> - [[#1563](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1563)] Update FAST CI/CD workflows so it can work with ID_TOKEN and Gitlab 15+ ([LucaPrete](https://github.com/LucaPrete)) <!-- 2023-08-03 16:09:45+00:00 -->
@ -42,6 +84,10 @@ All notable changes to this project will be documented in this file.
### MODULES ### MODULES
- [[#1581](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1581)] **incompatible change:** Remove firewall policy management from resource management modules ([ludoo](https://github.com/ludoo)) <!-- 2023-08-09 11:23:08+00:00 -->
- [[#1580](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1580)] Apigee addons ([apichick](https://github.com/apichick)) <!-- 2023-08-09 06:33:20+00:00 -->
- [[#1576](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1576)] **incompatible change:** Refactor firewall policy module ([ludoo](https://github.com/ludoo)) <!-- 2023-08-08 16:57:59+00:00 -->
- [[#1575](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1575)] Expose allow_net_admin feature in gke-cluster-autopilot module ([eunanhardy](https://github.com/eunanhardy)) <!-- 2023-08-07 15:03:51+00:00 -->
- [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) <!-- 2023-08-06 09:25:45+00:00 --> - [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) <!-- 2023-08-06 09:25:45+00:00 -->
- [[#1569](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1569)] Add support for cost management to GKE module ([ludoo](https://github.com/ludoo)) <!-- 2023-08-05 11:46:53+00:00 --> - [[#1569](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1569)] Add support for cost management to GKE module ([ludoo](https://github.com/ludoo)) <!-- 2023-08-05 11:46:53+00:00 -->
- [[#1568](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1568)] Add support for ipv6 to net-vpc module ([ludoo](https://github.com/ludoo)) <!-- 2023-08-05 11:07:27+00:00 --> - [[#1568](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1568)] Add support for ipv6 to net-vpc module ([ludoo](https://github.com/ludoo)) <!-- 2023-08-05 11:07:27+00:00 -->
@ -1437,7 +1483,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 - merge development branch with suite of new modules and end-to-end examples
<!-- markdown-link-check-disable --> <!-- markdown-link-check-disable -->
[Unreleased]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v24.0.0...HEAD [Unreleased]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v25.0.0...HEAD
[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 [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 [23.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v22.0.0...v23.0.0
[22.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v21.0.0...v22.0.0 [22.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v21.0.0...v22.0.0

View File

@ -30,8 +30,8 @@ The current list of modules supports most of the core foundational and networkin
Currently available modules: Currently available modules:
- **foundational** - [billing budget](./modules/billing-budget), [Cloud Identity group](./modules/cloud-identity-group/), [folder](./modules/folder), [service accounts](./modules/iam-service-account), [logging bucket](./modules/logging-bucket), [organization](./modules/organization), [project](./modules/project), [projects-data-source](./modules/projects-data-source) - **foundational** - [billing budget](./modules/billing-budget), [Cloud Identity group](./modules/cloud-identity-group/), [folder](./modules/folder), [service accounts](./modules/iam-service-account), [logging bucket](./modules/logging-bucket), [organization](./modules/organization), [project](./modules/project), [projects-data-source](./modules/projects-data-source)
- **networking** - [DNS](./modules/dns), [DNS Response Policy](./modules/dns-response-policy/), [Cloud Endpoints](./modules/endpoints), [address reservation](./modules/net-address), [NAT](./modules/net-cloudnat), [VLAN Attachment](./modules/net-vlan-attachment/), [External Application LB](./modules/net-lb-app-ext/), [External Passthrough Network LB](./modules/net-lb-ext), [Internal Application LB](./modules/net-lb-app-int), [Internal Passthrough Network LB](./modules/net-lb-int), [Internal Proxy Network LB](./modules/net-lb-proxy-int), [IPSec over Interconnect](./modules/net-ipsec-over-interconnect), [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC firewall policy](./modules/net-vpc-firewall-policy), [VPC peering](./modules/net-vpc-peering), [VPN dynamic](./modules/net-vpn-dynamic), [HA VPN](./modules/net-vpn-ha), [VPN static](./modules/net-vpn-static), [Service Directory](./modules/service-directory), [Secure Web Proxy](./modules/net-swp) - **networking** - [DNS](./modules/dns), [DNS Response Policy](./modules/dns-response-policy/), [Cloud Endpoints](./modules/endpoints), [address reservation](./modules/net-address), [NAT](./modules/net-cloudnat), [VLAN Attachment](./modules/net-vlan-attachment/), [External Application LB](./modules/net-lb-app-ext/), [External Passthrough Network LB](./modules/net-lb-ext), [Firewall policy](./modules/net-firewall-policy), [Internal Application LB](./modules/net-lb-app-int), [Internal Passthrough Network LB](./modules/net-lb-int), [Internal Proxy Network LB](./modules/net-lb-proxy-int), [IPSec over Interconnect](./modules/net-ipsec-over-interconnect), [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN dynamic](./modules/net-vpn-dynamic), [HA VPN](./modules/net-vpn-ha), [VPN static](./modules/net-vpn-static), [Service Directory](./modules/service-directory), [Secure Web Proxy](./modules/net-swp)
- **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [COS container](./modules/cloud-config-container/cos-generic-metadata/) (coredns, mysql, onprem, squid), [GKE cluster](./modules/gke-cluster-standard), [GKE hub](./modules/gke-hub), [GKE nodepool](./modules/gke-nodepool) - **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [COS container](./modules/cloud-config-container/cos-generic-metadata/) (coredns, mysql, onprem, squid), [GKE cluster](./modules/gke-cluster-standard), [GKE hub](./modules/gke-hub), [GKE nodepool](./modules/gke-nodepool), [GCVE private cloud](./modules/gcve-private-cloud)
- **data** - [AlloyDB instance](./modules/alloydb-instance), [BigQuery dataset](./modules/bigquery-dataset), [Bigtable instance](./modules/bigtable-instance), [Dataplex](./modules/dataplex), [Dataplex DataScan](./modules/dataplex-datascan/), [Cloud SQL instance](./modules/cloudsql-instance), [Data Catalog Policy Tag](./modules/data-catalog-policy-tag), [Datafusion](./modules/datafusion), [Dataproc](./modules/dataproc), [GCS](./modules/gcs), [Pub/Sub](./modules/pubsub) - **data** - [AlloyDB instance](./modules/alloydb-instance), [BigQuery dataset](./modules/bigquery-dataset), [Bigtable instance](./modules/bigtable-instance), [Dataplex](./modules/dataplex), [Dataplex DataScan](./modules/dataplex-datascan/), [Cloud SQL instance](./modules/cloudsql-instance), [Data Catalog Policy Tag](./modules/data-catalog-policy-tag), [Datafusion](./modules/datafusion), [Dataproc](./modules/dataproc), [GCS](./modules/gcs), [Pub/Sub](./modules/pubsub)
- **development** - [API Gateway](./modules/api-gateway), [Apigee](./modules/apigee), [Artifact Registry](./modules/artifact-registry), [Container Registry](./modules/container-registry), [Cloud Source Repository](./modules/source-repository) - **development** - [API Gateway](./modules/api-gateway), [Apigee](./modules/apigee), [Artifact Registry](./modules/artifact-registry), [Container Registry](./modules/container-registry), [Cloud Source Repository](./modules/source-repository)
- **security** - [Binauthz](./modules/binauthz/), [KMS](./modules/kms), [SecretManager](./modules/secret-manager), [VPC Service Control](./modules/vpc-sc) - **security** - [Binauthz](./modules/binauthz/), [KMS](./modules/kms), [SecretManager](./modules/secret-manager), [VPC Service Control](./modules/vpc-sc)

View File

@ -105,5 +105,5 @@ module "test" {
europe-west1 = "10.0.0.0/28" europe-west1 = "10.0.0.0/28"
} }
} }
# tftest modules=10 resources=64 # tftest modules=10 resources=65
``` ```

View File

@ -80,5 +80,5 @@ module "test" {
project_id = "my-project" project_id = "my-project"
hostname = "test.myorg.org" hostname = "test.myorg.org"
} }
# tftest modules=18 resources=61 # tftest modules=18 resources=62
``` ```

View File

@ -79,5 +79,5 @@ module "test" {
onprem_project_id = "my-onprem-project" onprem_project_id = "my-onprem-project"
hostname = "test.myorg.org" hostname = "test.myorg.org"
} }
# tftest modules=14 resources=77 # tftest modules=14 resources=78
``` ```

View File

@ -29,6 +29,7 @@ import warnings
import click import click
import google.auth import google.auth
import requests.exceptions
from google.auth.transport.requests import AuthorizedSession from google.auth.transport.requests import AuthorizedSession
@ -123,7 +124,8 @@ def fetch(request, delete=False):
else: else:
response = HTTP.post(request.url, headers=request.headers, response = HTTP.post(request.url, headers=request.headers,
data=json.dumps(request.data)) data=json.dumps(request.data))
except google.auth.exceptions.RefreshError as e: except (google.auth.exceptions.RefreshError,
requests.exceptions.ReadTimeout) as e:
raise SystemExit(e.args[0]) raise SystemExit(e.args[0])
try: try:
rdata = json.loads(response.content) rdata = json.loads(response.content)

View File

@ -2,3 +2,4 @@ click
functions-framework functions-framework
google-api-core google-api-core
google-cloud-monitoring google-cloud-monitoring
requests

View File

@ -13,21 +13,20 @@ This is the high level diagram:
This sample creates\updates several distinct groups of resources: This sample creates\updates several distinct groups of resources:
- projects - projects
- Deploy M4CE host project with [required services](https://cloud.google.com/migrate/compute-engine/docs/5.0/how-to/enable-services#enabling_required_services_on_the_host_project) on a new or existing project. - Deploy M4CE host project with [required services](https://cloud.google.com/migrate/compute-engine/docs/5.0/how-to/enable-services#enabling_required_services_on_the_host_project) on a new or existing project.
- M4CE target project prerequisites deployed on existing projects. - M4CE target project prerequisites deployed on existing projects.
- IAM - IAM
- Create a [service account](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/migrate-connector#step-3) used at runtime by the M4CE connector for data replication - Create a [service account](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/migrate-connector#step-3) used at runtime by the M4CE connector for data replication
- Grant [migration admin roles](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/enable-services#using_predefined_roles) to provided user accounts - Grant [migration admin roles](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/enable-services#using_predefined_roles) to provided user or group
- Grant [migration viewer role](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/enable-services#using_predefined_roles) to provided user accounts - Grant [migration viewer role](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/enable-services#using_predefined_roles) to provided user or group
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Variables ## Variables
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [migration_admin_users](variables.tf#L15) | List of users authorized to create a new M4CE sources and perform all other migration operations, in IAM format. | <code>list&#40;string&#41;</code> | ✓ | | | [migration_admin](variables.tf#L15) | User or group who can create a new M4CE sources and perform all other migration operations, in IAM format (`group:foo@example.com`). | <code>string</code> | ✓ | |
| [migration_target_projects](variables.tf#L20) | List of target projects for m4ce workload migrations. | <code>list&#40;string&#41;</code> | ✓ | | | [migration_target_projects](variables.tf#L20) | List of target projects for m4ce workload migrations. | <code>list&#40;string&#41;</code> | ✓ | |
| [migration_viewer_users](variables.tf#L25) | List of users authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [migration_viewer](variables.tf#L25) | User or group authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format (`group:foo@example.com`). | <code>string</code> | | <code>null</code> |
| [project_create](variables.tf#L31) | Parameters for the creation of the new project to host the M4CE backend. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [project_create](variables.tf#L31) | Parameters for the creation of the new project to host the M4CE backend. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [project_name](variables.tf#L40) | Name of an existing project or of the new project assigned as M4CE host project. | <code>string</code> | | <code>&#34;m4ce-host-project-000&#34;</code> | | [project_name](variables.tf#L40) | Name of an existing project or of the new project assigned as M4CE host project. | <code>string</code> | | <code>&#34;m4ce-host-project-000&#34;</code> |
@ -36,9 +35,7 @@ This sample creates\updates several distinct groups of resources:
| name | description | sensitive | | name | description | sensitive |
|---|---|:---:| |---|---|:---:|
| [m4ce_gmanaged_service_account](outputs.tf#L15) | Google managed service account created automatically during the migrate connector registration.. It is used by M4CE to perform activities on target projects. | | | [m4ce_gmanaged_service_account](outputs.tf#L15) | Google managed service account created automatically during the migrate connector registration.. It is used by M4CE to perform activities on target projects. | |
<!-- END TFDOC --> <!-- END TFDOC -->
## Test ## Test
```hcl ```hcl
@ -48,8 +45,8 @@ module "test" {
billing_account_id = "1234-ABCD-1234" billing_account_id = "1234-ABCD-1234"
parent = "folders/1234563" parent = "folders/1234563"
} }
migration_admin_users = ["user:admin@example.com"] migration_admin = "user:admin@example.com"
migration_viewer_users = ["user:viewer@example.com"] migration_viewer = "user:viewer@example.com"
migration_target_projects = [module.test-target-project.name] migration_target_projects = [module.test-target-project.name]
depends_on = [ depends_on = [
module.test-target-project module.test-target-project

View File

@ -19,11 +19,11 @@ module "host-project" {
: null : null
) )
name = var.project_name name = var.project_name
parent = (var.project_create != null parent = (
var.project_create != null
? var.project_create.parent ? var.project_create.parent
: null : null
) )
services = [ services = [
"cloudresourcemanager.googleapis.com", "cloudresourcemanager.googleapis.com",
"compute.googleapis.com", "compute.googleapis.com",
@ -33,14 +33,24 @@ module "host-project" {
"servicecontrol.googleapis.com", "servicecontrol.googleapis.com",
"vmmigration.googleapis.com", "vmmigration.googleapis.com",
] ]
project_create = var.project_create != null project_create = var.project_create != null
iam_bindings_additive = {
iam_additive = { admin_sa_key_admin = {
"roles/iam.serviceAccountKeyAdmin" = var.migration_admin_users, role = "roles/iam.serviceAccountKeyAdmin"
"roles/iam.serviceAccountCreator" = var.migration_admin_users, member = var.migration_admin
"roles/vmmigration.admin" = var.migration_admin_users, }
"roles/vmmigration.viewer" = var.migration_viewer_users, admin_sa_creator = {
role = "roles/iam.serviceAccountCreator"
member = var.migration_admin
}
admin_vmm_admin = {
role = "roles/vmmigration.admin"
member = var.migration_admin
}
viewer_vmm_viewer = {
role = "roles/vmmigration.viewer"
member = var.migration_viewer
}
} }
} }
@ -56,7 +66,6 @@ module "target-projects" {
source = "../../../../modules/project" source = "../../../../modules/project"
name = each.key name = each.key
project_create = false project_create = false
services = [ services = [
"servicemanagement.googleapis.com", "servicemanagement.googleapis.com",
"servicecontrol.googleapis.com", "servicecontrol.googleapis.com",
@ -64,10 +73,18 @@ module "target-projects" {
"cloudresourcemanager.googleapis.com", "cloudresourcemanager.googleapis.com",
"compute.googleapis.com" "compute.googleapis.com"
] ]
iam_bindings_additive = {
iam_additive = { admin_project_iam_admin = {
"roles/resourcemanager.projectIamAdmin" = var.migration_admin_users, role = "roles/resourcemanager.projectIamAdmin"
"roles/compute.viewer" = var.migration_admin_users, member = var.migration_admin
"roles/iam.serviceAccountUser" = var.migration_admin_users }
admin_compute_viewer = {
role = "roles/compute.viewer"
member = var.migration_admin
}
admin_sa_user = {
role = "roles/iam.serviceAccountUser"
member = var.migration_admin
}
} }
} }

View File

@ -12,9 +12,9 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
variable "migration_admin_users" { variable "migration_admin" {
description = "List of users authorized to create a new M4CE sources and perform all other migration operations, in IAM format." description = "User or group who can create a new M4CE sources and perform all other migration operations, in IAM format (`group:foo@example.com`)."
type = list(string) type = string
} }
variable "migration_target_projects" { variable "migration_target_projects" {
@ -22,10 +22,10 @@ variable "migration_target_projects" {
type = list(string) type = list(string)
} }
variable "migration_viewer_users" { variable "migration_viewer" {
description = "List of users authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format." description = "User or group authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format (`group:foo@example.com`)."
type = list(string) type = string
default = [] default = null
} }
variable "project_create" { variable "project_create" {

View File

@ -13,34 +13,33 @@ This is the high level diagram:
This sample creates\update several distinct groups of resources: This sample creates\update several distinct groups of resources:
- projects - projects
- M4CE host project with [required services](https://cloud.google.com/migrate/compute-engine/docs/5.0/how-to/enable-services#enabling_required_services_on_the_host_project) deployed on a new or existing project. - M4CE host project with [required services](https://cloud.google.com/migrate/compute-engine/docs/5.0/how-to/enable-services#enabling_required_services_on_the_host_project) deployed on a new or existing project.
- M4CE target project prerequisites deployed on existing projects. - M4CE target project prerequisites deployed on existing projects.
- IAM - IAM
- Create a [service account](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/migrate-connector#step-3) used at runtime by the M4CE connector for data replication - Create a [service account](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/migrate-connector#step-3) used at runtime by the M4CE connector for data replication
- Grant [migration admin roles](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/enable-services#using_predefined_roles) to provided user accounts. - Grant [migration admin roles](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/enable-services#using_predefined_roles) to provided user or group.
- Grant [migration viewer role](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/enable-services#using_predefined_roles) to provided user accounts. - Grant [migration viewer role](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/enable-services#using_predefined_roles) to provided user or group.
- Grant [roles on shared VPC](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/target-project#configure-permissions) to migration admins - Grant [roles on shared VPC](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/target-project#configure-permissions) to migration user or group
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Variables ## Variables
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [migration_admin_users](variables.tf#L15) | List of users authorized to create a new M4CE sources and perform all other migration operations, in IAM format. | <code>list&#40;string&#41;</code> | ✓ | | | [migration_admin](variables.tf#L15) | User or group who can create a new M4CE sources and perform all other migration operations, in IAM format (`group:foo@example.com`). | <code>string</code> | ✓ | |
| [migration_target_projects](variables.tf#L20) | List of target projects for m4ce workload migrations. | <code>list&#40;string&#41;</code> | ✓ | | | [migration_target_projects](variables.tf#L20) | List of target projects for m4ce workload migrations. | <code>list&#40;string&#41;</code> | ✓ | |
| [sharedvpc_host_projects](variables.tf#L45) | List of host projects that share a VPC with the selected target projects. | <code>list&#40;string&#41;</code> | ✓ | | | [sharedvpc_host_projects](variables.tf#L46) | List of host projects that share a VPC with the selected target projects. | <code>list&#40;string&#41;</code> | ✓ | |
| [migration_viewer_users](variables.tf#L25) | List of users authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [migration_viewer](variables.tf#L25) | User or group authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format (`group:foo@example.com`). | <code>string</code> | | <code>null</code> |
| [project_create](variables.tf#L30) | Parameters for the creation of the new project to host the M4CE backend. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [project_create](variables.tf#L31) | Parameters for the creation of the new project to host the M4CE backend. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [project_name](variables.tf#L39) | Name of an existing project or of the new project assigned as M4CE host project. | <code>string</code> | | <code>&#34;m4ce-host-project-000&#34;</code> | | [project_name](variables.tf#L40) | Name of an existing project or of the new project assigned as M4CE host project. | <code>string</code> | | <code>&#34;m4ce-host-project-000&#34;</code> |
## Outputs ## Outputs
| name | description | sensitive | | name | description | sensitive |
|---|---|:---:| |---|---|:---:|
| [m4ce_gmanaged_service_account](outputs.tf#L15) | Google managed service account created automatically during the migrate connector registration. It is used by M4CE to perform activities on target projects. | | | [m4ce_gmanaged_service_account](outputs.tf#L15) | Google managed service account created automatically during the migrate connector registration. It is used by M4CE to perform activities on target projects. | |
<!-- END TFDOC --> <!-- END TFDOC -->
## Manual Steps ## Manual Steps
Once this blueprint is deployed the M4CE [m4ce_gmanaged_service_account](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/target-sa-compute-engine#configuring_the_default_service_account) has to be configured to grant the access to the shared VPC and allow the deploy of Compute Engine instances as the result of the migration. Once this blueprint is deployed the M4CE [m4ce_gmanaged_service_account](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/target-sa-compute-engine#configuring_the_default_service_account) has to be configured to grant the access to the shared VPC and allow the deploy of Compute Engine instances as the result of the migration.
## Test ## Test
@ -52,8 +51,8 @@ module "test" {
billing_account_id = "1234-ABCD-1234" billing_account_id = "1234-ABCD-1234"
parent = "folders/1234563" parent = "folders/1234563"
} }
migration_admin_users = ["user:admin@example.com"] migration_admin = "user:admin@example.com"
migration_viewer_users = ["user:viewer@example.com"] migration_viewer = "user:viewer@example.com"
migration_target_projects = [module.test-target-project.name] migration_target_projects = [module.test-target-project.name]
sharedvpc_host_projects = [module.test-sharedvpc-host-project.name] sharedvpc_host_projects = [module.test-sharedvpc-host-project.name]
depends_on = [ depends_on = [

View File

@ -23,7 +23,6 @@ module "host-project" {
? var.project_create.parent ? var.project_create.parent
: null : null
) )
services = [ services = [
"cloudresourcemanager.googleapis.com", "cloudresourcemanager.googleapis.com",
"compute.googleapis.com", "compute.googleapis.com",
@ -33,14 +32,24 @@ module "host-project" {
"servicecontrol.googleapis.com", "servicecontrol.googleapis.com",
"vmmigration.googleapis.com", "vmmigration.googleapis.com",
] ]
project_create = var.project_create != null project_create = var.project_create != null
iam_bindings_additive = {
iam_additive = { admin_sa_key_admin = {
"roles/iam.serviceAccountKeyAdmin" = var.migration_admin_users, role = "roles/iam.serviceAccountKeyAdmin"
"roles/iam.serviceAccountCreator" = var.migration_admin_users, member = var.migration_admin
"roles/vmmigration.admin" = var.migration_admin_users, }
"roles/vmmigration.viewer" = var.migration_viewer_users, admin_sa_creator = {
role = "roles/iam.serviceAccountCreator"
member = var.migration_admin
}
admin_vmm_admin = {
role = "roles/vmmigration.admin"
member = var.migration_admin
}
viewer_vmm_viewer = {
role = "roles/vmmigration.viewer"
member = var.migration_viewer
}
} }
} }
@ -51,12 +60,10 @@ module "m4ce-service-account" {
} }
module "target-projects" { module "target-projects" {
for_each = toset(var.migration_target_projects) for_each = toset(var.migration_target_projects)
source = "../../../../modules/project" source = "../../../../modules/project"
name = each.key name = each.key
project_create = false project_create = false
services = [ services = [
"cloudresourcemanager.googleapis.com", "cloudresourcemanager.googleapis.com",
"compute.googleapis.com", "compute.googleapis.com",
@ -64,21 +71,27 @@ module "target-projects" {
"servicemanagement.googleapis.com", "servicemanagement.googleapis.com",
"servicecontrol.googleapis.com", "servicecontrol.googleapis.com",
] ]
iam_bindings_additive = {
iam_additive = { admin_project_iam_admin = {
"roles/resourcemanager.projectIamAdmin" = var.migration_admin_users, role = "roles/resourcemanager.projectIamAdmin"
"roles/iam.serviceAccountUser" = var.migration_admin_users, member = var.migration_admin
}
admin_sa_user = {
role = "roles/iam.serviceAccountUser"
member = var.migration_admin
}
} }
} }
module "sharedvpc_host_project" { module "sharedvpc_host_project" {
for_each = toset(var.sharedvpc_host_projects) for_each = toset(var.sharedvpc_host_projects)
source = "../../../../modules/project" source = "../../../../modules/project"
name = each.key name = each.key
project_create = false project_create = false
iam_bindings_additive = {
iam_additive = { admin_compute_viewer = {
"roles/compute.viewer" = var.migration_admin_users, role = "roles/compute.viewer"
member = var.migration_admin
}
} }
} }

View File

@ -12,9 +12,9 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
variable "migration_admin_users" { variable "migration_admin" {
description = "List of users authorized to create a new M4CE sources and perform all other migration operations, in IAM format." description = "User or group who can create a new M4CE sources and perform all other migration operations, in IAM format (`group:foo@example.com`)."
type = list(string) type = string
} }
variable "migration_target_projects" { variable "migration_target_projects" {
@ -22,11 +22,12 @@ variable "migration_target_projects" {
type = list(string) type = list(string)
} }
variable "migration_viewer_users" { variable "migration_viewer" {
description = "List of users authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format." description = "User or group authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format (`group:foo@example.com`)."
type = list(string) type = string
default = [] default = null
} }
variable "project_create" { variable "project_create" {
description = "Parameters for the creation of the new project to host the M4CE backend." description = "Parameters for the creation of the new project to host the M4CE backend."
type = object({ type = object({

View File

@ -13,21 +13,20 @@ This is the high level diagram:
This sample creates several distinct groups of resources: This sample creates several distinct groups of resources:
- projects - projects
- M4CE host project with [required services](https://cloud.google.com/migrate/compute-engine/docs/5.0/how-to/enable-services#enabling_required_services_on_the_host_project) deployed on a new or existing project. - M4CE host project with [required services](https://cloud.google.com/migrate/compute-engine/docs/5.0/how-to/enable-services#enabling_required_services_on_the_host_project) deployed on a new or existing project.
- networking - networking
- Default VPC network - Default VPC network
- IAM - IAM
- One [service account](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/migrate-connector#step-3) used at runtime by the M4CE connector for data replication - One [service account](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/migrate-connector#step-3) used at runtime by the M4CE connector for data replication
- Grant [migration admin roles](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/enable-services#using_predefined_roles) to admin user accounts - Grant [migration admin roles](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/enable-services#using_predefined_roles) to admin user or group
- Grant [migration viewer role](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/enable-services#using_predefined_roles) to viewer user accounts - Grant [migration viewer role](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/enable-services#using_predefined_roles) to viewer user or group
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Variables ## Variables
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [migration_admin_users](variables.tf#L15) | List of users authorized to create a new M4CE sources and perform all other migration operations, in IAM format. | <code>list&#40;string&#41;</code> | ✓ | | | [migration_admin](variables.tf#L15) | User or group who can create a new M4CE sources and perform all other migration operations, in IAM format (`group:foo@example.com`). | <code>string</code> | ✓ | |
| [migration_viewer_users](variables.tf#L20) | List of users authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [migration_viewer](variables.tf#L20) | User or group authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format (`group:foo@example.com`). | <code>string</code> | | <code>null</code> |
| [project_create](variables.tf#L26) | Parameters for the creation of the new project to host the M4CE backend. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [project_create](variables.tf#L26) | Parameters for the creation of the new project to host the M4CE backend. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [project_name](variables.tf#L35) | Name of an existing project or of the new project assigned as M4CE host an target project. | <code>string</code> | | <code>&#34;m4ce-host-project-000&#34;</code> | | [project_name](variables.tf#L35) | Name of an existing project or of the new project assigned as M4CE host an target project. | <code>string</code> | | <code>&#34;m4ce-host-project-000&#34;</code> |
| [vpc_config](variables.tf#L41) | Parameters to create a simple VPC on the M4CE project. | <code title="object&#40;&#123;&#10; ip_cidr_range &#61; string,&#10; region &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; ip_cidr_range &#61; &#34;10.200.0.0&#47;20&#34;,&#10; region &#61; &#34;us-west2&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | | [vpc_config](variables.tf#L41) | Parameters to create a simple VPC on the M4CE project. | <code title="object&#40;&#123;&#10; ip_cidr_range &#61; string,&#10; region &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; ip_cidr_range &#61; &#34;10.200.0.0&#47;20&#34;,&#10; region &#61; &#34;us-west2&#34;&#10;&#125;">&#123;&#8230;&#125;</code> |
@ -37,9 +36,7 @@ This sample creates several distinct groups of resources:
| name | description | sensitive | | name | description | sensitive |
|---|---|:---:| |---|---|:---:|
| [m4ce_gmanaged_service_account](outputs.tf#L15) | Google managed service account created automatically during the migrate connector registration. It is used by M4CE to perform activities on target projects. | | | [m4ce_gmanaged_service_account](outputs.tf#L15) | Google managed service account created automatically during the migrate connector registration. It is used by M4CE to perform activities on target projects. | |
<!-- END TFDOC --> <!-- END TFDOC -->
## Test ## Test
```hcl ```hcl
@ -49,8 +46,8 @@ module "test" {
billing_account_id = "1234-ABCD-1234" billing_account_id = "1234-ABCD-1234"
parent = "folders/1234563" parent = "folders/1234563"
} }
migration_admin_users = ["user:admin@example.com"] migration_admin = "user:admin@example.com"
migration_viewer_users = ["user:viewer@example.com"] migration_viewer = "user:viewer@example.com"
} }
# tftest modules=5 resources=22 # tftest modules=5 resources=22
``` ```

View File

@ -23,7 +23,6 @@ module "landing-project" {
? var.project_create.parent ? var.project_create.parent
: null : null
) )
services = [ services = [
"cloudresourcemanager.googleapis.com", "cloudresourcemanager.googleapis.com",
"compute.googleapis.com", "compute.googleapis.com",
@ -34,14 +33,24 @@ module "landing-project" {
"servicecontrol.googleapis.com", "servicecontrol.googleapis.com",
"vmmigration.googleapis.com" "vmmigration.googleapis.com"
] ]
project_create = var.project_create != null project_create = var.project_create != null
iam_bindings_additive = {
iam_additive = { admin_sa_key_admin = {
"roles/iam.serviceAccountKeyAdmin" = var.migration_admin_users, role = "roles/iam.serviceAccountKeyAdmin"
"roles/iam.serviceAccountCreator" = var.migration_admin_users, member = var.migration_admin
"roles/vmmigration.admin" = var.migration_admin_users, }
"roles/vmmigration.viewer" = var.migration_viewer_users admin_sa_creator = {
role = "roles/iam.serviceAccountCreator"
member = var.migration_admin
}
admin_vmm_admin = {
role = "roles/vmmigration.admin"
member = var.migration_admin
}
viewer_vmm_viewer = {
role = "roles/vmmigration.viewer"
member = var.migration_viewer
}
} }
} }

View File

@ -12,15 +12,15 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
variable "migration_admin_users" { variable "migration_admin" {
description = "List of users authorized to create a new M4CE sources and perform all other migration operations, in IAM format." description = "User or group who can create a new M4CE sources and perform all other migration operations, in IAM format (`group:foo@example.com`)."
type = list(string) type = string
} }
variable "migration_viewer_users" { variable "migration_viewer" {
description = "List of users authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format." description = "User or group authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format (`group:foo@example.com`)."
type = list(string) type = string
default = [] default = null
} }
variable "project_create" { variable "project_create" {

View File

@ -93,3 +93,19 @@ Once done testing, you can clean up resources by running `terraform destroy`.
| [vm_public_ip_address](outputs.tf#L39) | Azure VM public IP address. | | | [vm_public_ip_address](outputs.tf#L39) | Azure VM public IP address. | |
<!-- END TFDOC --> <!-- END TFDOC -->
<!--
## Test
```hcl
module "test" {
source = "./fabric/blueprints/cloud-operations/workload-identity-federation"
project_create = {
billing_account_id = "1234-ABCD-1234"
parent = "folders/1234563"
}
project_id = "test-prj"
}
# tftest modules=5 resources=33
```
-->

View File

@ -32,8 +32,11 @@ module "prj" {
"sts.googleapis.com", "sts.googleapis.com",
] ]
project_create = var.project_create != null project_create = var.project_create != null
iam_additive = { iam_bindings_additive = {
"roles/viewer" : [module.sa.iam_email] sa_viewer = {
member = module.sa.iam_email
role = "roles/viewer"
}
} }
} }

View File

@ -85,13 +85,14 @@ This implementation is intentionally minimal and easy to read. A real world use
The example supports the configuration of a Shared VPC as an input variable. The example supports the configuration of a Shared VPC as an input variable.
To deploy the solution on a Shared VPC, you have to configure the `network_config` variable: To deploy the solution on a Shared VPC, you have to configure the `network_config` variable:
``` ```hcl
network_config = { network_config = {
host_project = "PROJECT_ID" host_project = "PROJECT_ID"
network_self_link = "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/global/networks/VPC_NAME" network_self_link = "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/global/networks/VPC_NAME"
subnet_self_link = "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/regions/$REGION/subnetworks/SUBNET_NAME" subnet_self_link = "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/regions/$REGION/subnetworks/SUBNET_NAME"
cloudsql_psa_range = "10.60.0.0/24" cloudsql_psa_range = "10.60.0.0/24"
} }
# tftest skip
``` ```
To run this example, the Shared VPC project needs to have: To run this example, the Shared VPC project needs to have:
@ -137,7 +138,6 @@ terraform destroy
The above command will delete the associated resources so there will be no billable charges made afterwards. The above command will delete the associated resources so there will be no billable charges made afterwards.
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Variables ## Variables
| name | description | type | required | default | | name | description | type | required | default |
@ -145,13 +145,14 @@ The above command will delete the associated resources so there will be no billa
| [postgres_user_password](variables.tf#L40) | `postgres` user password. | <code>string</code> | ✓ | | | [postgres_user_password](variables.tf#L40) | `postgres` user password. | <code>string</code> | ✓ | |
| [prefix](variables.tf#L45) | Prefix used for resource names. | <code>string</code> | ✓ | | | [prefix](variables.tf#L45) | Prefix used for resource names. | <code>string</code> | ✓ | |
| [project_id](variables.tf#L63) | Project id, references existing project if `project_create` is null. | <code>string</code> | ✓ | | | [project_id](variables.tf#L63) | Project id, references existing project if `project_create` is null. | <code>string</code> | ✓ | |
| [data_eng_principals](variables.tf#L17) | Groups with Service Account Token creator role on service accounts in IAM format, only user supported on CloudSQL, eg 'user@domain.com'. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [data_eng_principal](variables.tf#L17) | Group or user in IAM format (`group:foo@example.com`) with permissions to access resources and impersonate service accounts. | <code>string</code> | | <code>null</code> |
| [network_config](variables.tf#L23) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; network_self_link &#61; string&#10; subnet_self_link &#61; string&#10; cloudsql_psa_range &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [network_config](variables.tf#L23) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; network_self_link &#61; string&#10; subnet_self_link &#61; string&#10; cloudsql_psa_range &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [postgres_database](variables.tf#L34) | `postgres` database. | <code>string</code> | | <code>&#34;guestbook&#34;</code> | | [postgres_database](variables.tf#L34) | `postgres` database. | <code>string</code> | | <code>&#34;guestbook&#34;</code> |
| [project_create](variables.tf#L54) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [project_create](variables.tf#L54) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [regions](variables.tf#L68) | Map of instance_name => location where instances will be deployed. | <code>map&#40;string&#41;</code> | | <code title="&#123;&#10; primary &#61; &#34;europe-west1&#34;&#10; replica &#61; &#34;europe-west3&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | | [regions](variables.tf#L68) | Map of instance_name => location where instances will be deployed. | <code>map&#40;string&#41;</code> | | <code title="&#123;&#10; primary &#61; &#34;europe-west1&#34;&#10; replica &#61; &#34;europe-west3&#34;&#10;&#125;">&#123;&#8230;&#125;</code> |
| [service_encryption_keys](variables.tf#L81) | Cloud KMS keys to use to encrypt resources. Provide a key for each reagion configured. | <code>map&#40;string&#41;</code> | | <code>null</code> | | [service_encryption_keys](variables.tf#L81) | Cloud KMS keys to use to encrypt resources. Provide a key for each reagion configured. | <code>map&#40;string&#41;</code> | | <code>null</code> |
| [sql_configuration](variables.tf#L87) | Cloud SQL configuration. | <code title="object&#40;&#123;&#10; availability_type &#61; string&#10; database_version &#61; string&#10; psa_range &#61; string&#10; tier &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; availability_type &#61; &#34;REGIONAL&#34;&#10; database_version &#61; &#34;POSTGRES_13&#34;&#10; psa_range &#61; &#34;10.60.0.0&#47;16&#34;&#10; tier &#61; &#34;db-g1-small&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | | [sql_configuration](variables.tf#L87) | Cloud SQL configuration. | <code title="object&#40;&#123;&#10; availability_type &#61; string&#10; database_version &#61; string&#10; psa_range &#61; string&#10; tier &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; availability_type &#61; &#34;REGIONAL&#34;&#10; database_version &#61; &#34;POSTGRES_13&#34;&#10; psa_range &#61; &#34;10.60.0.0&#47;16&#34;&#10; tier &#61; &#34;db-g1-small&#34;&#10;&#125;">&#123;&#8230;&#125;</code> |
| [sql_users](variables.tf#L103) | Cloud SQL user emails. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
## Outputs ## Outputs
@ -162,16 +163,14 @@ The above command will delete the associated resources so there will be no billa
| [demo_commands](outputs.tf#L27) | Demo commands. | | | [demo_commands](outputs.tf#L27) | Demo commands. | |
| [ips](outputs.tf#L36) | IP address of each instance. | | | [ips](outputs.tf#L36) | IP address of each instance. | |
| [project_id](outputs.tf#L41) | ID of the project containing all the instances. | | | [project_id](outputs.tf#L41) | ID of the project containing all the instances. | |
| [service_accounts](outputs.tf#L46) | Service Accounts. | | | [service_account](outputs.tf#L46) | SQL client service Accounts. | |
<!-- END TFDOC --> <!-- END TFDOC -->
## Test ## Test
```hcl ```hcl
module "test" { module "test" {
source = "./fabric/blueprints/data-solutions/cloudsql-multiregion/" source = "./fabric/blueprints/data-solutions/cloudsql-multiregion/"
data_eng_principals = ["dataeng@example.com"] data_eng_principal = "group:dataeng@example.com"
postgres_user_password = "my-root-password" postgres_user_password = "my-root-password"
project_id = "project" project_id = "project"
project_create = { project_create = {
@ -180,5 +179,5 @@ module "test" {
} }
prefix = "prefix" prefix = "prefix"
} }
# tftest modules=10 resources=52 # tftest modules=9 resources=43
``` ```

View File

@ -39,7 +39,7 @@ module "db" {
} }
resource "google_sql_user" "users" { resource "google_sql_user" "users" {
for_each = toset(var.data_eng_principals) for_each = toset(var.sql_users)
project = module.project.project_id project = module.project.project_id
name = each.value name = each.value
instance = module.db.name instance = module.db.name
@ -47,8 +47,7 @@ resource "google_sql_user" "users" {
} }
resource "google_sql_user" "service-account" { resource "google_sql_user" "service-account" {
for_each = toset(var.data_eng_principals) project = module.project.project_id
project = module.project.project_id
# Omit the .gserviceaccount.com suffix in the email # Omit the .gserviceaccount.com suffix in the email
name = regex("(.+)(.gserviceaccount)", module.service-account-sql.email)[0] name = regex("(.+)(.gserviceaccount)", module.service-account-sql.email)[0]
instance = module.db.name instance = module.db.name

View File

@ -15,54 +15,26 @@
*/ */
locals { locals {
data_eng_principals_iam = [ iam_roles = {
for k in var.data_eng_principals : data_eng = [
"user:${k}" "roles/owner"
]
iam = {
# GCS roles
"roles/storage.objectAdmin" = [
"serviceAccount:${module.project.service_accounts.robots.sql}",
module.service-account-gcs.iam_email,
] ]
# CloudSQL sql_robot = [
"roles/cloudsql.admin" = local.data_eng_principals_iam "roles/compute.networkUser",
"roles/cloudsql.client" = concat( "roles/storage.objectAdmin"
local.data_eng_principals_iam, ]
[module.service-account-sql.iam_email] sql_sa = [
) "roles/cloudsql.client",
"roles/cloudsql.instanceUser" = concat( "roles/cloudsql.instanceUser"
local.data_eng_principals_iam,
[module.service-account-sql.iam_email]
)
# compute engineering
"roles/compute.instanceAdmin.v1" = local.data_eng_principals_iam
"roles/compute.osLogin" = local.data_eng_principals_iam
"roles/compute.viewer" = local.data_eng_principals_iam
"roles/iap.tunnelResourceAccessor" = local.data_eng_principals_iam
# common roles
"roles/logging.admin" = local.data_eng_principals_iam
"roles/iam.serviceAccountUser" = concat(
local.data_eng_principals_iam
)
"roles/iam.serviceAccountTokenCreator" = concat(
local.data_eng_principals_iam
)
# network roles
"roles/compute.networkUser" = [
"serviceAccount:${module.project.service_accounts.robots.sql}"
] ]
} }
shared_vpc_project = try(var.network_config.host_project, null) shared_vpc_project = try(var.network_config.host_project, null)
use_shared_vpc = var.network_config != null
subnet = ( subnet = (
local.use_shared_vpc local.use_shared_vpc
? var.network_config.subnet_self_link ? var.network_config.subnet_self_link
: values(module.vpc.0.subnet_self_links)[0] : values(module.vpc.0.subnet_self_links)[0]
) )
use_shared_vpc = var.network_config != null
vpc_self_link = ( vpc_self_link = (
local.use_shared_vpc local.use_shared_vpc
? var.network_config.network_self_link ? var.network_config.network_self_link
@ -77,8 +49,26 @@ module "project" {
billing_account = try(var.project_create.billing_account_id, null) billing_account = try(var.project_create.billing_account_id, null)
project_create = var.project_create != null project_create = var.project_create != null
prefix = var.project_create == null ? null : var.prefix prefix = var.project_create == null ? null : var.prefix
iam = var.project_create != null ? local.iam : {} iam_bindings_additive = merge(
iam_additive = var.project_create == null ? local.iam : {} var.data_eng_principal == null ? {} : {
for r in local.iam_roles.data_eng : "data_eng-${r}" => {
member = var.data_eng_principal
role = r
}
},
{
for r in local.iam_roles.sql_robot : "sql_robot-${r}" => {
member = "serviceAccount:${module.project.service_accounts.robots.sql}"
role = r
}
},
{
for r in local.iam_roles.sql_sa : "sql_sa-${r}" => {
member = module.service-account-sql.iam_email
role = r
}
}
)
services = [ services = [
"cloudkms.googleapis.com", "cloudkms.googleapis.com",
"compute.googleapis.com", "compute.googleapis.com",
@ -92,12 +82,10 @@ module "project" {
"storage.googleapis.com", "storage.googleapis.com",
"storage-component.googleapis.com", "storage-component.googleapis.com",
] ]
shared_vpc_service_config = local.shared_vpc_project == null ? null : { shared_vpc_service_config = local.shared_vpc_project == null ? null : {
attach = true attach = true
host_project = local.shared_vpc_project host_project = local.shared_vpc_project
} }
service_encryption_key_ids = { service_encryption_key_ids = {
compute = try(values(var.service_encryption_keys), []) compute = try(values(var.service_encryption_keys), [])
sql = try(values(var.service_encryption_keys), []) sql = try(values(var.service_encryption_keys), [])
@ -120,7 +108,6 @@ module "vpc" {
region = var.regions.primary region = var.regions.primary
} }
] ]
psa_config = { psa_config = {
ranges = { cloud-sql = var.sql_configuration.psa_range } ranges = { cloud-sql = var.sql_configuration.psa_range }
routes = null routes = null
@ -145,3 +132,14 @@ module "nat" {
name = "${var.prefix}-default" name = "${var.prefix}-default"
router_network = module.vpc.0.name router_network = module.vpc.0.name
} }
module "gcs" {
source = "../../../modules/gcs"
project_id = module.project.project_id
prefix = var.prefix
name = "data"
location = var.regions.primary
storage_class = "REGIONAL"
encryption_key = var.service_encryption_keys != null ? try(var.service_encryption_keys[var.regions.primary], null) : null
force_destroy = true
}

View File

@ -43,10 +43,7 @@ output "project_id" {
value = module.project.project_id value = module.project.project_id
} }
output "service_accounts" { output "service_account" {
description = "Service Accounts." description = "SQL client service Accounts."
value = { value = module.service-account-sql.email
"gcs" = module.service-account-gcs.email
"sql" = module.service-account-sql.email
}
} }

View File

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
variable "data_eng_principals" { variable "data_eng_principal" {
description = "Groups with Service Account Token creator role on service accounts in IAM format, only user supported on CloudSQL, eg 'user@domain.com'." description = "Group or user in IAM format (`group:foo@example.com`) with permissions to access resources and impersonate service accounts."
type = list(string) type = string
default = [] default = null
} }
variable "network_config" { variable "network_config" {
@ -99,3 +99,9 @@ variable "sql_configuration" {
tier = "db-g1-small" tier = "db-g1-small"
} }
} }
variable "sql_users" {
description = "Cloud SQL user emails."
type = list(string)
default = []
}

View File

@ -1,34 +1,39 @@
# Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key # Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key
This blueprint creates a Private instance of [Cloud Composer version 2](https://cloud.google.com/composer/docs/composer-2/composer-versioning-overview) on a VPC with a dedicated service account. Cloud Composer 2 is the new major version for Cloud Composer that supports: This blueprint creates a Private instance of [Cloud Composer version 2](https://cloud.google.com/composer/docs/composer-2/composer-versioning-overview) on a VPC with a dedicated service account. Cloud Composer 2 is the new major version for Cloud Composer that supports:
- environment autoscaling
- workloads configuration: CPU, memory, and storage parameters for Airflow workers, schedulers, web server, and database. - environment autoscaling
- workloads configuration: CPU, memory, and storage parameters for Airflow workers, schedulers, web server, and database.
Please consult the [documentation page](https://cloud.google.com/composer/docs/composer-2/composer-versioning-overview) for an exhaustive comparison between Composer Version 1 and Version 2. Please consult the [documentation page](https://cloud.google.com/composer/docs/composer-2/composer-versioning-overview) for an exhaustive comparison between Composer Version 1 and Version 2.
The solution will use: The solution will use:
- Cloud Composer
- VPC with Private Service Access to deploy resources, if no Shared VPC configuration provided. - Cloud Composer
- Google Cloud NAT to access internet resources, if no Shared VPC configuration provided. - VPC with Private Service Access to deploy resources, if no Shared VPC configuration provided.
- Google Cloud NAT to access internet resources, if no Shared VPC configuration provided.
The solution supports as inputs: The solution supports as inputs:
- Shared VPC
- Cloud KMS CMEK keys - Shared VPC
- Cloud KMS CMEK keys
This is the high level diagram: This is the high level diagram:
![Cloud Composer 2 architecture overview](./diagram.png "Cloud Composer 2 architecture overview") ![Cloud Composer 2 architecture overview](./diagram.png "Cloud Composer 2 architecture overview")
# Requirements ## Requirements
This blueprint will deploy all its resources into the project defined by the project_id variable. Please note that we assume this project already exists. However, if you provide the appropriate values to the `project_create` variable, the project will be created as part of the deployment. This blueprint will deploy all its resources into the project defined by the project_id variable. Please note that we assume this project already exists. However, if you provide the appropriate values to the `project_create` variable, the project will be created as part of the deployment.
If `project_create` is left to null, the identity performing the deployment needs the owner role on the project defined by the `project_id` variable. Otherwise, the identity performing the deployment needs `resourcemanager.projectCreator` on the resource hierarchy node specified by `project_create.parent` and `billing.user` on the billing account specified by `project_create.billing_account_id`. If `project_create` is left to null, the identity performing the deployment needs the owner role on the project defined by the `project_id` variable. Otherwise, the identity performing the deployment needs `resourcemanager.projectCreator` on the resource hierarchy node specified by `project_create.parent` and `billing.user` on the billing account specified by `project_create.billing_account_id`.
# Deployment ## Deployment
Run Terraform init: Run Terraform init:
```bash ```bash
$ terraform init terraform init
``` ```
Configure the Terraform variable in your terraform.tfvars file. You need to specify at least the following variables: Configure the Terraform variable in your terraform.tfvars file. You need to specify at least the following variables:
@ -41,23 +46,28 @@ prefix = "lc"
You can run now: You can run now:
```bash ```bash
$ terraform apply terraform apply
``` ```
You can now connect to your instance. You can now connect to your instance.
# Customizations ## Customizations
### VPC
## VPC
If a shared VPC is not configured, a VPC will be created within the project. The following IP ranges will be used: If a shared VPC is not configured, a VPC will be created within the project. The following IP ranges will be used:
- Cloudsql: `10.20.10.0/24` - Cloudsql: `10.20.10.0/24`
- GKE: `10.20.11.0/28` - GKE: `10.20.11.0/28`
Change the code as needed to match your needed configuration, remember that these addresses should not overlap with any other range used in network. Change the code as needed to match your needed configuration, remember that these addresses should not overlap with any other range used in network.
## Shared VPC
As is often the case in real-world configurations, this blueprint accepts as input an existing [`Shared-VPC`](https://cloud.google.com/vpc/docs/shared-vpc) via the `network_config` variable. ### Shared VPC
As is often the case in real-world configurations, this blueprint accepts as input an existing [`Shared-VPC`](https://cloud.google.com/vpc/docs/shared-vpc) via the `network_config` variable.
Example: Example:
```tfvars ```tfvars
network_config = { network_config = {
host_project = "PROJECT" host_project = "PROJECT"
@ -68,42 +78,47 @@ network_config = {
services = "services" services = "services"
} }
} }
# tftest skip
``` ```
Make sure that: Make sure that:
- The GKE API (`container.googleapis.com`) is enabled in the VPC host project. - The GKE API (`container.googleapis.com`) is enabled in the VPC host project.
- The subnet has secondary ranges configured with 2 ranges: - The subnet has secondary ranges configured with 2 ranges:
- pods: `/22` example: `10.10.8.0/22` - pods: `/22` example: `10.10.8.0/22`
- services = `/24` example: 10.10.12.0/24` - services = `/24` example: 10.10.12.0/24`
- Firewall rules are set, as described in the [documentation](https://cloud.google.com/composer/docs/composer-2/configure-private-ip#step_3_configure_firewall_rules) - Firewall rules are set, as described in the [documentation](https://cloud.google.com/composer/docs/composer-2/configure-private-ip#step_3_configure_firewall_rules)
In order to run the example and deploy Cloud Composer on a shared VPC the identity running Terraform must have the following IAM role on the Shared VPC Host project. In order to run the example and deploy Cloud Composer on a shared VPC the identity running Terraform must have the following IAM role on the Shared VPC Host project.
- Compute Network Admin (roles/compute.networkAdmin)
- Compute Shared VPC Admin (roles/compute.xpnAdmin) - Compute Network Admin (roles/compute.networkAdmin)
- Compute Shared VPC Admin (roles/compute.xpnAdmin)
## Encryption ## Encryption
As is often the case in real-world configurations, this blueprint accepts as input an existing [`Cloud KMS keys`](https://cloud.google.com/kms/docs/cmek) via the `service_encryption_keys` variable.
As is often the case in real-world configurations, this blueprint accepts as input an existing [`Cloud KMS keys`](https://cloud.google.com/kms/docs/cmek) via the `service_encryption_keys` variable.
Example: Example:
```tfvars ```tfvars
service_encryption_keys = { service_encryption_keys = {
`europe/west1` = `projects/PROJECT/locations/REGION/keyRings/KR_NAME/cryptoKeys/KEY_NAME` `europe/west1` = `projects/PROJECT/locations/REGION/keyRings/KR_NAME/cryptoKeys/KEY_NAME`
} }
# tftest skip
``` ```
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Variables ## Variables
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [prefix](variables.tf#L82) | Prefix used for resource names. | <code>string</code> | ✓ | | | [prefix](variables.tf#L83) | Prefix used for resource names. | <code>string</code> | ✓ | |
| [project_id](variables.tf#L100) | Project id, references existing project if `project_create` is null. | <code>string</code> | ✓ | | | [project_id](variables.tf#L101) | Project id, references existing project if `project_create` is null. | <code>string</code> | ✓ | |
| [composer_config](variables.tf#L17) | Composer environment configuration. It accepts only following attributes: `environment_size`, `software_config` and `workloads_config`. See [attribute reference](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/composer_environment#argument-reference---cloud-composer-2) for details on settings variables. | <code title="object&#40;&#123;&#10; environment_size &#61; string&#10; software_config &#61; any&#10; workloads_config &#61; object&#40;&#123;&#10; scheduler &#61; object&#40;&#10; &#123;&#10; cpu &#61; number&#10; memory_gb &#61; number&#10; storage_gb &#61; number&#10; count &#61; number&#10; &#125;&#10; &#41;&#10; web_server &#61; object&#40;&#10; &#123;&#10; cpu &#61; number&#10; memory_gb &#61; number&#10; storage_gb &#61; number&#10; &#125;&#10; &#41;&#10; worker &#61; object&#40;&#10; &#123;&#10; cpu &#61; number&#10; memory_gb &#61; number&#10; storage_gb &#61; number&#10; min_count &#61; number&#10; max_count &#61; number&#10; &#125;&#10; &#41;&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; environment_size &#61; &#34;ENVIRONMENT_SIZE_SMALL&#34;&#10; software_config &#61; &#123;&#10; image_version &#61; &#34;composer-2-airflow-2&#34;&#10; &#125;&#10; workloads_config &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> | | [composer_config](variables.tf#L17) | Composer environment configuration. It accepts only following attributes: `environment_size`, `software_config` and `workloads_config`. See [attribute reference](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/composer_environment#argument-reference---cloud-composer-2) for details on settings variables. | <code title="object&#40;&#123;&#10; environment_size &#61; string&#10; software_config &#61; any&#10; workloads_config &#61; object&#40;&#123;&#10; scheduler &#61; object&#40;&#10; &#123;&#10; cpu &#61; number&#10; memory_gb &#61; number&#10; storage_gb &#61; number&#10; count &#61; number&#10; &#125;&#10; &#41;&#10; web_server &#61; object&#40;&#10; &#123;&#10; cpu &#61; number&#10; memory_gb &#61; number&#10; storage_gb &#61; number&#10; &#125;&#10; &#41;&#10; worker &#61; object&#40;&#10; &#123;&#10; cpu &#61; number&#10; memory_gb &#61; number&#10; storage_gb &#61; number&#10; min_count &#61; number&#10; max_count &#61; number&#10; &#125;&#10; &#41;&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; environment_size &#61; &#34;ENVIRONMENT_SIZE_SMALL&#34;&#10; software_config &#61; &#123;&#10; image_version &#61; &#34;composer-2-airflow-2&#34;&#10; &#125;&#10; workloads_config &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> |
| [iam_groups_map](variables.tf#L58) | Map of Role => groups to be added on the project. Example: { \"roles/composer.admin\" = [\"group:gcp-data-engineers@example.com\"]}. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>null</code> | | [iam_bindings_additive](variables.tf#L58) | Map of Role => principal in IAM format (`group:foo@example.org`) to be added on the project. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [network_config](variables.tf#L64) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; network_self_link &#61; string&#10; subnet_self_link &#61; string&#10; composer_ip_ranges &#61; object&#40;&#123;&#10; cloudsql &#61; string&#10; gke_master &#61; string&#10; &#125;&#41;&#10; composer_secondary_ranges &#61; object&#40;&#123;&#10; pods &#61; string&#10; services &#61; string&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [network_config](variables.tf#L65) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; network_self_link &#61; string&#10; subnet_self_link &#61; string&#10; composer_ip_ranges &#61; object&#40;&#123;&#10; cloudsql &#61; string&#10; gke_master &#61; string&#10; &#125;&#41;&#10; composer_secondary_ranges &#61; object&#40;&#123;&#10; pods &#61; string&#10; services &#61; string&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [project_create](variables.tf#L91) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [project_create](variables.tf#L92) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [region](variables.tf#L105) | Reagion where instances will be deployed. | <code>string</code> | | <code>&#34;europe-west1&#34;</code> | | [region](variables.tf#L106) | Reagion where instances will be deployed. | <code>string</code> | | <code>&#34;europe-west1&#34;</code> |
| [service_encryption_keys](variables.tf#L111) | Cloud KMS keys to use to encrypt resources. Provide a key for each reagion in use. | <code>map&#40;string&#41;</code> | | <code>null</code> | | [service_encryption_keys](variables.tf#L112) | Cloud KMS keys to use to encrypt resources. Provide a key for each reagion in use. | <code>map&#40;string&#41;</code> | | <code>null</code> |
## Outputs ## Outputs
@ -111,7 +126,6 @@ service_encryption_keys = {
|---|---|:---:| |---|---|:---:|
| [composer_airflow_uri](outputs.tf#L17) | The URI of the Apache Airflow Web UI hosted within the Cloud Composer environment.. | | | [composer_airflow_uri](outputs.tf#L17) | The URI of the Apache Airflow Web UI hosted within the Cloud Composer environment.. | |
| [composer_dag_gcs](outputs.tf#L22) | The Cloud Storage prefix of the DAGs for the Cloud Composer environment. | | | [composer_dag_gcs](outputs.tf#L22) | The Cloud Storage prefix of the DAGs for the Cloud Composer environment. | |
<!-- END TFDOC --> <!-- END TFDOC -->
## Test ## Test

View File

@ -15,14 +15,8 @@
*/ */
locals { locals {
iam = merge( # add Roles on Service Identities service account as per documentation
{ # https://cloud.google.com/composer/docs/composer-2/configure-shared-vpc#edit_permissions_for_the_google_apis_service_account
"roles/composer.worker" = [module.comp-sa.iam_email]
"roles/composer.ServiceAgentV2Ext" = ["serviceAccount:${module.project.service_accounts.robots.composer}"]
},
var.iam_groups_map
)
# Adding Roles on Service Identities Service account as per documentation: https://cloud.google.com/composer/docs/composer-2/configure-shared-vpc#edit_permissions_for_the_google_apis_service_account
_shared_vpc_bindings = { _shared_vpc_bindings = {
"roles/compute.networkUser" = [ "roles/compute.networkUser" = [
"prj-cloudservices", "prj-robot-gke" "prj-cloudservices", "prj-robot-gke"
@ -34,11 +28,16 @@ locals {
"prj-robot-gke" "prj-robot-gke"
] ]
} }
shared_vpc_role_members = { orch_subnet = (
prj-cloudservices = "serviceAccount:${module.project.service_accounts.cloud_services}" local.use_shared_vpc
prj-robot-gke = "serviceAccount:${module.project.service_accounts.robots.container-engine}" ? var.network_config.subnet_self_link
prj-robot-cs = "serviceAccount:${module.project.service_accounts.robots.composer}" : values(module.vpc.0.subnet_self_links)[0]
} )
orch_vpc = (
local.use_shared_vpc
? var.network_config.network_self_link
: module.vpc.0.self_link
)
# reassemble in a format suitable for for_each # reassemble in a format suitable for for_each
shared_vpc_bindings_map = { shared_vpc_bindings_map = {
for binding in flatten([ for binding in flatten([
@ -47,27 +46,24 @@ locals {
] ]
]) : "${binding.role}-${binding.member}" => binding ]) : "${binding.role}-${binding.member}" => binding
} }
shared_vpc_project = try(var.network_config.host_project, null) shared_vpc_project = try(var.network_config.host_project, null)
use_shared_vpc = var.network_config != null shared_vpc_role_members = {
prj-cloudservices = (
"serviceAccount:${module.project.service_accounts.cloud_services}"
)
prj-robot-gke = (
"serviceAccount:${module.project.service_accounts.robots.container-engine}"
)
prj-robot-cs = (
"serviceAccount:${module.project.service_accounts.robots.composer}"
)
}
use_shared_vpc = var.network_config != null
vpc_self_link = ( vpc_self_link = (
local.use_shared_vpc local.use_shared_vpc
? var.network_config.network_self_link ? var.network_config.network_self_link
: module.vpc.0.self_link : module.vpc.0.self_link
) )
orch_subnet = (
local.use_shared_vpc
? var.network_config.subnet_self_link
: values(module.vpc.0.subnet_self_links)[0]
)
orch_vpc = (
local.use_shared_vpc
? var.network_config.network_self_link
: module.vpc.0.self_link
)
} }
module "project" { module "project" {
@ -77,8 +73,24 @@ module "project" {
billing_account = try(var.project_create.billing_account_id, null) billing_account = try(var.project_create.billing_account_id, null)
project_create = var.project_create != null project_create = var.project_create != null
prefix = var.project_create == null ? null : var.prefix prefix = var.project_create == null ? null : var.prefix
iam = var.project_create != null ? local.iam : {} iam_bindings_additive = merge(
iam_additive = var.project_create == null ? local.iam : {} {
composer_worker = {
member = module.comp-sa.iam_email
role = "roles/composer.worker"
},
composer_service_agent = {
member = "serviceAccount:${module.project.service_accounts.robots.composer}"
role = "roles/composer.ServiceAgentV2Ext"
}
},
{
for k, v in var.iam_bindings_additive : "${k}:${v}" => {
member = v
role = k
}
}
)
services = [ services = [
"artifactregistry.googleapis.com", "artifactregistry.googleapis.com",
"cloudkms.googleapis.com", "cloudkms.googleapis.com",
@ -94,19 +106,13 @@ module "project" {
"storage.googleapis.com", "storage.googleapis.com",
"storage-component.googleapis.com", "storage-component.googleapis.com",
] ]
shared_vpc_service_config = local.shared_vpc_project == null ? null : { shared_vpc_service_config = local.shared_vpc_project == null ? null : {
attach = true attach = true
host_project = local.shared_vpc_project host_project = local.shared_vpc_project
} }
service_encryption_key_ids = { service_encryption_key_ids = {
composer = [try(lookup(var.service_encryption_keys, var.region, null), null)] composer = [try(lookup(var.service_encryption_keys, var.region, null), null)]
} }
service_config = {
disable_on_destroy = false, disable_dependent_services = false
}
} }
module "vpc" { module "vpc" {

View File

@ -55,10 +55,11 @@ variable "composer_config" {
} }
} }
variable "iam_groups_map" { variable "iam_bindings_additive" {
description = "Map of Role => groups to be added on the project. Example: { \"roles/composer.admin\" = [\"group:gcp-data-engineers@example.com\"]}." description = "Map of Role => principal in IAM format (`group:foo@example.org`) to be added on the project."
type = map(list(string)) type = map(list(string))
default = null nullable = false
default = {}
} }
variable "network_config" { variable "network_config" {

View File

@ -15,21 +15,28 @@
# tfdoc:file:description drop off project and resources. # tfdoc:file:description drop off project and resources.
locals { locals {
iam_drp = { drp_iam = {
"roles/bigquery.dataEditor" = [ data_engineers = [
module.drop-sa-bq-0.iam_email, local.groups_iam.data-engineers "roles/bigquery.dataEditor",
"roles/bigquery.user"
] ]
"roles/bigquery.user" = [ sa_drop_bq = [
module.load-sa-df-0.iam_email, local.groups_iam.data-engineers "roles/bigquery.dataEditor"
] ]
"roles/pubsub.publisher" = [module.drop-sa-ps-0.iam_email] sa_drop_cs = [
"roles/pubsub.subscriber" = [ "roles/storage.objectCreator"
module.orch-sa-cmp-0.iam_email, module.load-sa-df-0.iam_email
] ]
"roles/storage.objectCreator" = [module.drop-sa-cs-0.iam_email] sa_drop_ps = [
"roles/storage.objectViewer" = [module.orch-sa-cmp-0.iam_email] "roles/pubsub.publisher"
"roles/storage.objectAdmin" = [ ]
module.load-sa-df-0.iam_email, module.load-sa-df-0.iam_email sa_load = [
"roles/bigquery.user",
"roles/pubsub.subscriber",
"roles/storage.objectAdmin"
]
sa_orch = [
"roles/pubsub.subscriber",
"roles/storage.objectViewer"
] ]
} }
} }
@ -39,10 +46,14 @@ module "drop-project" {
parent = var.project_config.parent parent = var.project_config.parent
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null project_create = var.project_config.billing_account_id != null
prefix = var.project_config.billing_account_id == null ? null : var.prefix prefix = local.use_projects ? null : var.prefix
name = var.project_config.billing_account_id == null ? var.project_config.project_ids.drop : "${var.project_config.project_ids.drop}${local.project_suffix}" name = (
iam = var.project_config.billing_account_id != null ? local.iam_drp : null local.use_projects
iam_additive = var.project_config.billing_account_id == null ? local.iam_drp : null ? var.project_config.project_ids.drop
: "${var.project_config.project_ids.drop}${local.project_suffix}"
)
iam = local.use_projects ? {} : local.drp_iam_auth
iam_bindings_additive = !local.use_projects ? {} : local.drp_iam_additive
services = concat(var.project_services, [ services = concat(var.project_services, [
"bigquery.googleapis.com", "bigquery.googleapis.com",
"bigqueryreservation.googleapis.com", "bigqueryreservation.googleapis.com",
@ -59,8 +70,6 @@ module "drop-project" {
} }
} }
# Cloud Storage
module "drop-sa-cs-0" { module "drop-sa-cs-0" {
source = "../../../modules/iam-service-account" source = "../../../modules/iam-service-account"
project_id = module.drop-project.project_id project_id = module.drop-project.project_id
@ -89,8 +98,6 @@ module "drop-cs-0" {
# } # }
} }
# PubSub
module "drop-sa-ps-0" { module "drop-sa-ps-0" {
source = "../../../modules/iam-service-account" source = "../../../modules/iam-service-account"
project_id = module.drop-project.project_id project_id = module.drop-project.project_id
@ -111,8 +118,6 @@ module "drop-ps-0" {
kms_key = try(local.service_encryption_keys.pubsub, null) kms_key = try(local.service_encryption_keys.pubsub, null)
} }
# BigQuery
module "drop-sa-bq-0" { module "drop-sa-bq-0" {
source = "../../../modules/iam-service-account" source = "../../../modules/iam-service-account"
project_id = module.drop-project.project_id project_id = module.drop-project.project_id

View File

@ -15,46 +15,38 @@
# tfdoc:file:description Load project and VPC. # tfdoc:file:description Load project and VPC.
locals { locals {
iam_load = { load_iam = {
"roles/bigquery.jobUser" = [module.load-sa-df-0.iam_email] data_engineers = [
"roles/dataflow.admin" = [ "roles/dataflow.admin"
module.orch-sa-cmp-0.iam_email,
module.load-sa-df-0.iam_email,
local.groups_iam.data-engineers
] ]
"roles/dataflow.developer" = [ robots_dataflow_load = [
local.groups_iam.data-engineers "roles/storage.objectAdmin"
]
sa_load = [
"roles/bigquery.jobUser",
"roles/dataflow.admin",
"roles/dataflow.worker",
"roles/storage.objectAdmin"
]
sa_orch = [
"roles/dataflow.admin"
] ]
"roles/dataflow.worker" = [module.load-sa-df-0.iam_email]
"roles/storage.objectAdmin" = local.load_service_accounts
} }
load_service_accounts = [
"serviceAccount:${module.load-project.service_accounts.robots.dataflow}",
module.load-sa-df-0.iam_email
]
load_subnet = (
local.use_shared_vpc
? var.network_config.subnet_self_links.orchestration
: values(module.load-vpc.0.subnet_self_links)[0]
)
load_vpc = (
local.use_shared_vpc
? var.network_config.network_self_link
: module.load-vpc.0.self_link
)
} }
# Project
module "load-project" { module "load-project" {
source = "../../../modules/project" source = "../../../modules/project"
parent = var.project_config.parent parent = var.project_config.parent
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null project_create = var.project_config.billing_account_id != null
prefix = var.project_config.billing_account_id == null ? null : var.prefix prefix = local.use_projects ? null : var.prefix
name = var.project_config.billing_account_id == null ? var.project_config.project_ids.load : "${var.project_config.project_ids.load}${local.project_suffix}" name = (
iam = var.project_config.billing_account_id != null ? local.iam_load : null local.use_projects
iam_additive = var.project_config.billing_account_id == null ? local.iam_load : null ? var.project_config.project_ids.load
: "${var.project_config.project_ids.load}${local.project_suffix}"
)
iam = local.use_projects ? {} : local.load_iam_auth
iam_bindings_additive = !local.use_projects ? {} : local.load_iam_additive
services = concat(var.project_services, [ services = concat(var.project_services, [
"bigquery.googleapis.com", "bigquery.googleapis.com",
"bigqueryreservation.googleapis.com", "bigqueryreservation.googleapis.com",
@ -106,8 +98,6 @@ module "load-cs-df-0" {
encryption_key = try(local.service_encryption_keys.storage, null) encryption_key = try(local.service_encryption_keys.storage, null)
} }
# internal VPC resources
module "load-vpc" { module "load-vpc" {
source = "../../../modules/net-vpc" source = "../../../modules/net-vpc"
count = local.use_shared_vpc ? 0 : 1 count = local.use_shared_vpc ? 0 : 1

View File

@ -15,58 +15,39 @@
# tfdoc:file:description Orchestration project and VPC. # tfdoc:file:description Orchestration project and VPC.
locals { locals {
iam_orch = { orch_iam = {
"roles/artifactregistry.admin" = [local.groups_iam.data-engineers] data_engineers = [
"roles/artifactregistry.reader" = [module.load-sa-df-0.iam_email] "roles/artifactregistry.admin",
"roles/bigquery.dataEditor" = [ "roles/bigquery.dataEditor",
module.load-sa-df-0.iam_email, "roles/bigquery.jobUser",
module.transf-sa-df-0.iam_email, "roles/cloudbuild.builds.editor",
local.groups_iam.data-engineers "roles/composer.environmentAndStorageObjectAdmin",
"roles/iam.serviceAccountUser",
"roles/iap.httpsResourceAccessor",
"roles/serviceusage.serviceUsageConsumer"
] ]
"roles/bigquery.jobUser" = [ robots_cloudbuild = [
module.orch-sa-cmp-0.iam_email, "roles/storage.objectAdmin"
local.groups_iam.data-engineers
] ]
"roles/cloudbuild.builds.editor" = [local.groups_iam.data-engineers] robots_composer = [
"roles/cloudbuild.serviceAgent" = [module.orch-sa-df-build.iam_email] "roles/composer.ServiceAgentV2Ext",
"roles/composer.admin" = [local.groups_iam.data-engineers] "roles/storage.objectAdmin"
"roles/composer.user" = [local.groups_iam.data-engineers]
"roles/composer.environmentAndStorageObjectAdmin" = [local.groups_iam.data-engineers]
"roles/composer.ServiceAgentV2Ext" = [
"serviceAccount:${module.orch-project.service_accounts.robots.composer}"
] ]
"roles/composer.worker" = [ sa_load = [
module.orch-sa-cmp-0.iam_email "roles/artifactregistry.reader",
"roles/bigquery.dataEditor",
"roles/storage.objectViewer"
] ]
"roles/iam.serviceAccountUser" = [ sa_orch = [
module.orch-sa-cmp-0.iam_email, local.groups_iam.data-engineers "roles/bigquery.jobUser",
"roles/composer.worker",
"roles/iam.serviceAccountUser",
"roles/storage.objectAdmin"
] ]
"roles/iap.httpsResourceAccessor" = [local.groups_iam.data-engineers] sa_transf_df = [
"roles/serviceusage.serviceUsageConsumer" = [local.groups_iam.data-engineers] "roles/bigquery.dataEditor"
"roles/storage.objectAdmin" = [
module.orch-sa-cmp-0.iam_email,
module.orch-sa-df-build.iam_email,
"serviceAccount:${module.orch-project.service_accounts.robots.composer}",
"serviceAccount:${module.orch-project.service_accounts.robots.cloudbuild}",
local.groups_iam.data-engineers
] ]
"roles/storage.objectViewer" = [module.load-sa-df-0.iam_email]
} }
orch_subnet = (
local.use_shared_vpc
? var.network_config.subnet_self_links.orchestration
: values(module.orch-vpc.0.subnet_self_links)[0]
)
orch_vpc = (
local.use_shared_vpc
? var.network_config.network_self_link
: module.orch-vpc.0.self_link
)
# Note: This formatting is needed for output purposes since the fabric artifact registry
# module doesn't yet expose the docker usage path of a registry folder in the needed format.
orch_docker_path = format("%s-docker.pkg.dev/%s/%s",
var.region, module.orch-project.project_id, module.orch-artifact-reg.name)
} }
module "orch-project" { module "orch-project" {
@ -74,15 +55,17 @@ module "orch-project" {
parent = var.project_config.parent parent = var.project_config.parent
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null project_create = var.project_config.billing_account_id != null
prefix = var.project_config.billing_account_id == null ? null : var.prefix prefix = local.use_projects ? null : var.prefix
name = ( name = (
var.project_config.billing_account_id == null local.use_projects
? var.project_config.project_ids.orc ? var.project_config.project_ids.orc
: "${var.project_config.project_ids.orc}${local.project_suffix}" : "${var.project_config.project_ids.orc}${local.project_suffix}"
) )
iam = var.project_config.billing_account_id != null ? local.iam_orch : null iam = local.use_projects ? {} : local.orch_iam_auth
iam_additive = var.project_config.billing_account_id == null ? local.iam_orch : null iam_bindings_additive = !local.use_projects ? {} : local.orch_iam_additive
oslogin = false compute_metadata = {
enable-oslogin = "false"
}
services = concat(var.project_services, [ services = concat(var.project_services, [
"artifactregistry.googleapis.com", "artifactregistry.googleapis.com",
"bigquery.googleapis.com", "bigquery.googleapis.com",
@ -112,8 +95,6 @@ module "orch-project" {
} }
} }
# Cloud Storage
module "orch-cs-0" { module "orch-cs-0" {
source = "../../../modules/gcs" source = "../../../modules/gcs"
project_id = module.orch-project.project_id project_id = module.orch-project.project_id
@ -124,8 +105,6 @@ module "orch-cs-0" {
encryption_key = try(local.service_encryption_keys.storage, null) encryption_key = try(local.service_encryption_keys.storage, null)
} }
# internal VPC resources
module "orch-vpc" { module "orch-vpc" {
source = "../../../modules/net-vpc" source = "../../../modules/net-vpc"
count = local.use_shared_vpc ? 0 : 1 count = local.use_shared_vpc ? 0 : 1

View File

@ -15,29 +15,25 @@
# tfdoc:file:description Transformation project and VPC. # tfdoc:file:description Transformation project and VPC.
locals { locals {
iam_trf = { trf_iam = {
"roles/bigquery.jobUser" = [ data_engineers = [
module.transf-sa-bq-0.iam_email, local.groups_iam.data-engineers "roles/bigquery.jobUser",
"roles/dataflow.admin"
] ]
"roles/dataflow.admin" = [ robots_dataflow_trf = [
module.orch-sa-cmp-0.iam_email, local.groups_iam.data-engineers "roles/storage.objectAdmin"
] ]
"roles/dataflow.worker" = [module.transf-sa-df-0.iam_email] sa_orch = [
"roles/storage.objectAdmin" = [ "roles/dataflow.admin"
module.transf-sa-df-0.iam_email, ]
"serviceAccount:${module.transf-project.service_accounts.robots.dataflow}" sa_transf_bq = [
"roles/bigquery.jobUser"
]
sa_transf_df = [
"roles/dataflow.worker",
"roles/storage.objectAdmin"
] ]
} }
transf_subnet = (
local.use_shared_vpc
? var.network_config.subnet_self_links.orchestration
: values(module.transf-vpc.0.subnet_self_links)[0]
)
transf_vpc = (
local.use_shared_vpc
? var.network_config.network_self_link
: module.transf-vpc.0.self_link
)
} }
module "transf-project" { module "transf-project" {
@ -45,10 +41,14 @@ module "transf-project" {
parent = var.project_config.parent parent = var.project_config.parent
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null project_create = var.project_config.billing_account_id != null
prefix = var.project_config.billing_account_id == null ? null : var.prefix prefix = local.use_projects ? null : var.prefix
name = var.project_config.billing_account_id == null ? var.project_config.project_ids.trf : "${var.project_config.project_ids.trf}${local.project_suffix}" name = (
iam = var.project_config.billing_account_id != null ? local.iam_trf : null local.use_projects
iam_additive = var.project_config.billing_account_id == null ? local.iam_trf : null ? var.project_config.project_ids.trf
: "${var.project_config.project_ids.trf}${local.project_suffix}"
)
iam = local.use_projects ? {} : local.trf_iam_auth
iam_bindings_additive = !local.use_projects ? {} : local.trf_iam_additive
services = concat(var.project_services, [ services = concat(var.project_services, [
"bigquery.googleapis.com", "bigquery.googleapis.com",
"bigqueryreservation.googleapis.com", "bigqueryreservation.googleapis.com",
@ -72,8 +72,6 @@ module "transf-project" {
} }
} }
# Cloud Storage
module "transf-sa-df-0" { module "transf-sa-df-0" {
source = "../../../modules/iam-service-account" source = "../../../modules/iam-service-account"
project_id = module.transf-project.project_id project_id = module.transf-project.project_id
@ -101,8 +99,6 @@ module "transf-cs-df-0" {
encryption_key = try(local.service_encryption_keys.storage, null) encryption_key = try(local.service_encryption_keys.storage, null)
} }
# BigQuery
module "transf-sa-bq-0" { module "transf-sa-bq-0" {
source = "../../../modules/iam-service-account" source = "../../../modules/iam-service-account"
project_id = module.transf-project.project_id project_id = module.transf-project.project_id
@ -120,8 +116,6 @@ module "transf-sa-bq-0" {
} }
} }
# internal VPC resources
module "transf-vpc" { module "transf-vpc" {
source = "../../../modules/net-vpc" source = "../../../modules/net-vpc"
count = local.use_shared_vpc ? 0 : 1 count = local.use_shared_vpc ? 0 : 1

View File

@ -15,61 +15,48 @@
# tfdoc:file:description Data Warehouse projects. # tfdoc:file:description Data Warehouse projects.
locals { locals {
dwh_lnd_iam = {
"roles/bigquery.dataOwner" = [
module.load-sa-df-0.iam_email,
]
"roles/bigquery.dataViewer" = [
module.transf-sa-df-0.iam_email,
module.transf-sa-bq-0.iam_email,
local.groups_iam.data-engineers
]
"roles/bigquery.jobUser" = [
module.load-sa-df-0.iam_email, local.groups_iam.data-engineers
]
"roles/datacatalog.categoryAdmin" = [module.transf-sa-bq-0.iam_email]
"roles/datacatalog.tagTemplateViewer" = [local.groups_iam.data-engineers]
"roles/datacatalog.viewer" = [local.groups_iam.data-engineers]
"roles/storage.objectCreator" = [module.load-sa-df-0.iam_email]
"roles/storage.objectViewer" = [local.groups_iam.data-engineers]
}
dwh_iam = { dwh_iam = {
"roles/bigquery.dataOwner" = [ data_analysts = [
module.transf-sa-df-0.iam_email, "roles/bigquery.dataViewer",
module.transf-sa-bq-0.iam_email, "roles/bigquery.jobUser",
"roles/datacatalog.viewer",
"roles/storage.objectViewer"
] ]
"roles/bigquery.dataViewer" = [ data_engineers = [
local.groups_iam.data-analysts, "roles/bigquery.dataViewer",
local.groups_iam.data-engineers "roles/bigquery.jobUser",
"roles/datacatalog.viewer",
"roles/storage.objectViewer"
] ]
"roles/bigquery.jobUser" = [ sa_transf_bq = [
module.transf-sa-bq-0.iam_email, "roles/bigquery.dataOwner",
local.groups_iam.data-analysts, "roles/bigquery.jobUser"
local.groups_iam.data-engineers
] ]
"roles/datacatalog.tagTemplateViewer" = [ sa_transf_df = [
local.groups_iam.data-analysts, local.groups_iam.data-engineers "roles/bigquery.dataOwner",
"roles/storage.objectAdmin"
]
}
lnd_iam = {
data_engineers = [
"roles/bigquery.dataViewer",
"roles/bigquery.jobUser",
"roles/datacatalog.viewer",
"roles/storage.objectViewer"
]
sa_load = [
"roles/storage.objectCreator"
]
sa_transf_bq = [
"roles/bigquery.dataViewer",
"roles/datacatalog.categoryAdmin"
]
sa_transf_df = [
"roles/bigquery.dataOwner",
"roles/bigquery.dataViewer",
"roles/bigquery.jobUser"
] ]
"roles/datacatalog.viewer" = [
local.groups_iam.data-analysts, local.groups_iam.data-engineers
]
"roles/storage.objectViewer" = [
local.groups_iam.data-analysts, local.groups_iam.data-engineers
]
"roles/storage.objectAdmin" = [module.transf-sa-df-0.iam_email]
} }
dwh_services = concat(var.project_services, [
"bigquery.googleapis.com",
"bigqueryreservation.googleapis.com",
"bigquerystorage.googleapis.com",
"cloudkms.googleapis.com",
"compute.googleapis.com",
"dataflow.googleapis.com",
"pubsub.googleapis.com",
"servicenetworking.googleapis.com",
"storage.googleapis.com",
"storage-component.googleapis.com"
])
} }
# Project # Project
@ -79,11 +66,15 @@ module "dwh-lnd-project" {
parent = var.project_config.parent parent = var.project_config.parent
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null project_create = var.project_config.billing_account_id != null
prefix = var.project_config.billing_account_id == null ? null : var.prefix prefix = local.use_projects ? null : var.prefix
name = var.project_config.billing_account_id == null ? var.project_config.project_ids.dwh-lnd : "${var.project_config.project_ids.dwh-lnd}${local.project_suffix}" name = (
iam = var.project_config.billing_account_id != null ? local.dwh_lnd_iam : {} local.use_projects
iam_additive = var.project_config.billing_account_id == null ? local.dwh_lnd_iam : {} ? var.project_config.project_ids.dwh-lnd
services = local.dwh_services : "${var.project_config.project_ids.dwh-lnd}${local.project_suffix}"
)
iam = local.use_projects ? {} : local.lnd_iam_auth
iam_bindings_additive = !local.use_projects ? {} : local.lnd_iam_additive
services = local.dwh_services
service_encryption_key_ids = { service_encryption_key_ids = {
bq = [try(local.service_encryption_keys.bq, null)] bq = [try(local.service_encryption_keys.bq, null)]
storage = [try(local.service_encryption_keys.storage, null)] storage = [try(local.service_encryption_keys.storage, null)]
@ -95,11 +86,15 @@ module "dwh-cur-project" {
parent = var.project_config.parent parent = var.project_config.parent
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null project_create = var.project_config.billing_account_id != null
prefix = var.project_config.billing_account_id == null ? null : var.prefix prefix = local.use_projects ? null : var.prefix
name = var.project_config.billing_account_id == null ? var.project_config.project_ids.dwh-cur : "${var.project_config.project_ids.dwh-cur}${local.project_suffix}" name = (
iam = var.project_config.billing_account_id != null ? local.dwh_iam : {} local.use_projects
iam_additive = var.project_config.billing_account_id == null ? local.dwh_iam : {} ? var.project_config.project_ids.dwh-cur
services = local.dwh_services : "${var.project_config.project_ids.dwh-cur}${local.project_suffix}"
)
iam = local.use_projects ? {} : local.dwh_iam_auth
iam_bindings_additive = !local.use_projects ? {} : local.dwh_iam_additive
services = local.dwh_services
service_encryption_key_ids = { service_encryption_key_ids = {
bq = [try(local.service_encryption_keys.bq, null)] bq = [try(local.service_encryption_keys.bq, null)]
storage = [try(local.service_encryption_keys.storage, null)] storage = [try(local.service_encryption_keys.storage, null)]
@ -111,19 +106,21 @@ module "dwh-conf-project" {
parent = var.project_config.parent parent = var.project_config.parent
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null project_create = var.project_config.billing_account_id != null
prefix = var.project_config.billing_account_id == null ? null : var.prefix prefix = local.use_projects ? null : var.prefix
name = var.project_config.billing_account_id == null ? var.project_config.project_ids.dwh-conf : "${var.project_config.project_ids.dwh-conf}${local.project_suffix}" name = (
iam = var.project_config.billing_account_id != null ? local.dwh_iam : null local.use_projects
iam_additive = var.project_config.billing_account_id == null ? local.dwh_iam : null ? var.project_config.project_ids.dwh-conf
services = local.dwh_services : "${var.project_config.project_ids.dwh-conf}${local.project_suffix}"
)
iam = local.use_projects ? {} : local.dwh_iam_auth
iam_bindings_additive = !local.use_projects ? {} : local.dwh_iam_additive
services = local.dwh_services
service_encryption_key_ids = { service_encryption_key_ids = {
bq = [try(local.service_encryption_keys.bq, null)] bq = [try(local.service_encryption_keys.bq, null)]
storage = [try(local.service_encryption_keys.storage, null)] storage = [try(local.service_encryption_keys.storage, null)]
} }
} }
# Bigquery
module "dwh-lnd-bq-0" { module "dwh-lnd-bq-0" {
source = "../../../modules/bigquery-dataset" source = "../../../modules/bigquery-dataset"
project_id = module.dwh-lnd-project.project_id project_id = module.dwh-lnd-project.project_id
@ -148,8 +145,6 @@ module "dwh-conf-bq-0" {
encryption_key = try(local.service_encryption_keys.bq, null) encryption_key = try(local.service_encryption_keys.bq, null)
} }
# Cloud storage
module "dwh-lnd-cs-0" { module "dwh-lnd-cs-0" {
source = "../../../modules/gcs" source = "../../../modules/gcs"
project_id = module.dwh-lnd-project.project_id project_id = module.dwh-lnd-project.project_id

View File

@ -15,47 +15,56 @@
# tfdoc:file:description common project. # tfdoc:file:description common project.
locals { locals {
iam_common = { cmn_iam = {
"roles/dlp.admin" = [local.groups_iam.data-security] data_analysts = [
"roles/dlp.estimatesAdmin" = [local.groups_iam.data-engineers] # uncomment if access to all tagged columns is needed
"roles/dlp.reader" = [local.groups_iam.data-engineers] # "roles/datacatalog.categoryFineGrainedReader",
"roles/dlp.user" = [ "roles/datacatalog.viewer"
module.load-sa-df-0.iam_email,
module.transf-sa-df-0.iam_email,
local.groups_iam.data-engineers
] ]
"roles/datacatalog.admin" = [local.groups_iam.data-security] data_engineers = [
"roles/datacatalog.viewer" = [ "roles/dlp.estimatesAdmin",
module.load-sa-df-0.iam_email, "roles/dlp.reader",
module.transf-sa-df-0.iam_email, "roles/dlp.user"
module.transf-sa-bq-0.iam_email,
local.groups_iam.data-analysts
] ]
"roles/datacatalog.categoryFineGrainedReader" = [ data_security = [
module.transf-sa-df-0.iam_email, "roles/datacatalog.admin",
module.transf-sa-bq-0.iam_email, "roles/dlp.admin"
# Uncomment if you want to grant access to `data-analyst` to all columns tagged. ]
# local.groups_iam.data-analysts sa_load = [
"roles/datacatalog.viewer",
"roles/dlp.user"
]
sa_transf_bq = [
"roles/datacatalog.categoryFineGrainedReader",
"roles/datacatalog.viewer"
]
sa_transf_df = [
"roles/datacatalog.categoryFineGrainedReader",
"roles/datacatalog.viewer",
"roles/dlp.user"
] ]
} }
} }
module "common-project" { module "common-project" {
source = "../../../modules/project" source = "../../../modules/project"
parent = var.project_config.parent parent = var.project_config.parent
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null project_create = var.project_config.billing_account_id != null
prefix = var.project_config.billing_account_id == null ? null : var.prefix prefix = local.use_projects ? null : var.prefix
name = var.project_config.billing_account_id == null ? var.project_config.project_ids.common : "${var.project_config.project_ids.common}${local.project_suffix}" name = (
iam = var.project_config.billing_account_id != null ? local.iam_common : null local.use_projects
iam_additive = var.project_config.billing_account_id == null ? local.iam_common : null ? var.project_config.project_ids.common
: "${var.project_config.project_ids.common}${local.project_suffix}"
)
iam = local.use_projects ? {} : local.cmn_iam_auth
iam_bindings_additive = !local.use_projects ? {} : local.cmn_iam_additive
services = concat(var.project_services, [ services = concat(var.project_services, [
"datacatalog.googleapis.com", "datacatalog.googleapis.com",
"dlp.googleapis.com", "dlp.googleapis.com",
]) ])
} }
# Data Catalog Policy tag
module "common-datacatalog" { module "common-datacatalog" {
source = "../../../modules/data-catalog-policy-tag" source = "../../../modules/data-catalog-policy-tag"
project_id = module.common-project.project_id project_id = module.common-project.project_id
@ -64,7 +73,8 @@ module "common-datacatalog" {
tags = var.data_catalog_tags tags = var.data_catalog_tags
} }
# To create KMS keys in the common project: uncomment this section and assigne key links accondingly in local.service_encryption_keys variable # To create KMS keys in the common project: uncomment this section
# and assign key links accondingly in local.service_encryption_keys variable
# module "cmn-kms-0" { # module "cmn-kms-0" {
# source = "../../../modules/kms" # source = "../../../modules/kms"

View File

@ -19,6 +19,10 @@ module "exp-project" {
parent = var.project_config.parent parent = var.project_config.parent
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null project_create = var.project_config.billing_account_id != null
prefix = var.project_config.billing_account_id == null ? null : var.prefix prefix = local.use_projects ? null : var.prefix
name = var.project_config.billing_account_id == null ? var.project_config.project_ids.exp : "${var.project_config.project_ids.exp}${local.project_suffix}" name = (
local.use_projects
? var.project_config.project_ids.exp
: "${var.project_config.project_ids.exp}${local.project_suffix}"
)
} }

View File

@ -202,8 +202,7 @@ project_config = {
parent = "folders/1111111111" parent = "folders/1111111111"
billing_account_id = "1111111-2222222-33333333" billing_account_id = "1111111-2222222-33333333"
} }
organization_domain = "domain.com" organization_domain = "domain.com"
~
``` ```
For more fine details check variables on [`variables.tf`](./variables.tf) and update according to the desired configuration. Remember to create team groups described [below](#groups). For more fine details check variables on [`variables.tf`](./variables.tf) and update according to the desired configuration. Remember to create team groups described [below](#groups).
@ -229,8 +228,7 @@ module "data-platform" {
} }
prefix = "myprefix" prefix = "myprefix"
} }
# tftest modules=43 resources=279
# tftest modules=43 resources=285
``` ```
## Customizations ## Customizations
@ -262,19 +260,19 @@ You can find examples in the `[demo](./demo)` folder.
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [organization_domain](variables.tf#L159) | Organization domain. | <code>string</code> | ✓ | | | [organization_domain](variables.tf#L164) | Organization domain. | <code>string</code> | ✓ | |
| [prefix](variables.tf#L164) | Prefix used for resource names. | <code>string</code> | ✓ | | | [prefix](variables.tf#L169) | Prefix used for resource names. | <code>string</code> | ✓ | |
| [project_config](variables.tf#L173) | 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. | <code title="object&#40;&#123;&#10; billing_account_id &#61; optional&#40;string, null&#41;&#10; parent &#61; string&#10; project_ids &#61; optional&#40;object&#40;&#123;&#10; drop &#61; string&#10; load &#61; string&#10; orc &#61; string&#10; trf &#61; string&#10; dwh-lnd &#61; string&#10; dwh-cur &#61; string&#10; dwh-conf &#61; string&#10; common &#61; string&#10; exp &#61; string&#10; &#125;&#41;, &#123;&#10; drop &#61; &#34;drp&#34;&#10; load &#61; &#34;lod&#34;&#10; orc &#61; &#34;orc&#34;&#10; trf &#61; &#34;trf&#34;&#10; dwh-lnd &#61; &#34;dwh-lnd&#34;&#10; dwh-cur &#61; &#34;dwh-cur&#34;&#10; dwh-conf &#61; &#34;dwh-conf&#34;&#10; common &#61; &#34;cmn&#34;&#10; exp &#61; &#34;exp&#34;&#10; &#125;&#10; &#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | [project_config](variables.tf#L178) | 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. | <code title="object&#40;&#123;&#10; billing_account_id &#61; optional&#40;string, null&#41;&#10; parent &#61; string&#10; project_ids &#61; optional&#40;object&#40;&#123;&#10; drop &#61; string&#10; load &#61; string&#10; orc &#61; string&#10; trf &#61; string&#10; dwh-lnd &#61; string&#10; dwh-cur &#61; string&#10; dwh-conf &#61; string&#10; common &#61; string&#10; exp &#61; string&#10; &#125;&#41;, &#123;&#10; drop &#61; &#34;drp&#34;&#10; load &#61; &#34;lod&#34;&#10; orc &#61; &#34;orc&#34;&#10; trf &#61; &#34;trf&#34;&#10; dwh-lnd &#61; &#34;dwh-lnd&#34;&#10; dwh-cur &#61; &#34;dwh-cur&#34;&#10; dwh-conf &#61; &#34;dwh-conf&#34;&#10; common &#61; &#34;cmn&#34;&#10; exp &#61; &#34;exp&#34;&#10; &#125;&#10; &#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [composer_config](variables.tf#L17) | Cloud Composer config. | <code title="object&#40;&#123;&#10; disable_deployment &#61; optional&#40;bool&#41;&#10; environment_size &#61; optional&#40;string, &#34;ENVIRONMENT_SIZE_SMALL&#34;&#41;&#10; software_config &#61; optional&#40;object&#40;&#123;&#10; airflow_config_overrides &#61; optional&#40;any&#41;&#10; pypi_packages &#61; optional&#40;any&#41;&#10; env_variables &#61; optional&#40;map&#40;string&#41;&#41;&#10; image_version &#61; string&#10; &#125;&#41;, &#123;&#10; image_version &#61; &#34;composer-2-airflow-2&#34;&#10; &#125;&#41;&#10; workloads_config &#61; optional&#40;object&#40;&#123;&#10; scheduler &#61; optional&#40;object&#40;&#10; &#123;&#10; cpu &#61; number&#10; memory_gb &#61; number&#10; storage_gb &#61; number&#10; count &#61; number&#10; &#125;&#10; &#41;, &#123;&#10; cpu &#61; 0.5&#10; memory_gb &#61; 1.875&#10; storage_gb &#61; 1&#10; count &#61; 1&#10; &#125;&#41;&#10; web_server &#61; optional&#40;object&#40;&#10; &#123;&#10; cpu &#61; number&#10; memory_gb &#61; number&#10; storage_gb &#61; number&#10; &#125;&#10; &#41;, &#123;&#10; cpu &#61; 0.5&#10; memory_gb &#61; 1.875&#10; storage_gb &#61; 1&#10; &#125;&#41;&#10; worker &#61; optional&#40;object&#40;&#10; &#123;&#10; cpu &#61; number&#10; memory_gb &#61; number&#10; storage_gb &#61; number&#10; min_count &#61; number&#10; max_count &#61; number&#10; &#125;&#10; &#41;, &#123;&#10; cpu &#61; 0.5&#10; memory_gb &#61; 1.875&#10; storage_gb &#61; 1&#10; min_count &#61; 1&#10; max_count &#61; 3&#10; &#125;&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; environment_size &#61; &#34;ENVIRONMENT_SIZE_SMALL&#34;&#10; software_config &#61; &#123;&#10; image_version &#61; &#34;composer-2-airflow-2&#34;&#10; &#125;&#10; workloads_config &#61; &#123;&#10; scheduler &#61; &#123;&#10; cpu &#61; 0.5&#10; memory_gb &#61; 1.875&#10; storage_gb &#61; 1&#10; count &#61; 1&#10; &#125;&#10; web_server &#61; &#123;&#10; cpu &#61; 0.5&#10; memory_gb &#61; 1.875&#10; storage_gb &#61; 1&#10; &#125;&#10; worker &#61; &#123;&#10; cpu &#61; 0.5&#10; memory_gb &#61; 1.875&#10; storage_gb &#61; 1&#10; min_count &#61; 1&#10; max_count &#61; 3&#10; &#125;&#10; &#125;&#10;&#125;">&#123;&#8230;&#125;</code> | | [composer_config](variables.tf#L17) | Cloud Composer config. | <code title="object&#40;&#123;&#10; disable_deployment &#61; optional&#40;bool&#41;&#10; environment_size &#61; optional&#40;string, &#34;ENVIRONMENT_SIZE_SMALL&#34;&#41;&#10; software_config &#61; optional&#40;&#10; object&#40;&#123;&#10; airflow_config_overrides &#61; optional&#40;any&#41;&#10; pypi_packages &#61; optional&#40;any&#41;&#10; env_variables &#61; optional&#40;map&#40;string&#41;&#41;&#10; image_version &#61; string&#10; &#125;&#41;,&#10; &#123; image_version &#61; &#34;composer-2-airflow-2&#34; &#125;&#10; &#41;&#10; workloads_config &#61; optional&#40;&#10; object&#40;&#123;&#10; scheduler &#61; optional&#40;&#10; object&#40;&#123;&#10; cpu &#61; number&#10; memory_gb &#61; number&#10; storage_gb &#61; number&#10; count &#61; number&#10; &#125;&#41;,&#10; &#123;&#10; cpu &#61; 0.5&#10; memory_gb &#61; 1.875&#10; storage_gb &#61; 1&#10; count &#61; 1&#10; &#125;&#10; &#41;&#10; web_server &#61; optional&#40;&#10; object&#40;&#123;&#10; cpu &#61; number&#10; memory_gb &#61; number&#10; storage_gb &#61; number&#10; &#125;&#41;,&#10; &#123;&#10; cpu &#61; 0.5&#10; memory_gb &#61; 1.875&#10; storage_gb &#61; 1&#10; &#125;&#10; &#41;&#10; worker &#61; optional&#40;&#10; object&#40;&#123;&#10; cpu &#61; number&#10; memory_gb &#61; number&#10; storage_gb &#61; number&#10; min_count &#61; number&#10; max_count &#61; number&#10; &#125;&#41;,&#10; &#123;&#10; cpu &#61; 0.5&#10; memory_gb &#61; 1.875&#10; storage_gb &#61; 1&#10; min_count &#61; 1&#10; max_count &#61; 3&#10; &#125;&#10; &#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; environment_size &#61; &#34;ENVIRONMENT_SIZE_SMALL&#34;&#10; software_config &#61; &#123;&#10; image_version &#61; &#34;composer-2-airflow-2&#34;&#10; &#125;&#10; workloads_config &#61; &#123;&#10; scheduler &#61; &#123;&#10; cpu &#61; 0.5&#10; memory_gb &#61; 1.875&#10; storage_gb &#61; 1&#10; count &#61; 1&#10; &#125;&#10; web_server &#61; &#123;&#10; cpu &#61; 0.5&#10; memory_gb &#61; 1.875&#10; storage_gb &#61; 1&#10; &#125;&#10; worker &#61; &#123;&#10; cpu &#61; 0.5&#10; memory_gb &#61; 1.875&#10; storage_gb &#61; 1&#10; min_count &#61; 1&#10; max_count &#61; 3&#10; &#125;&#10; &#125;&#10;&#125;">&#123;&#8230;&#125;</code> |
| [data_catalog_tags](variables.tf#L100) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code title="&#123;&#10; &#34;3_Confidential&#34; &#61; &#123;&#125;&#10; &#34;2_Private&#34; &#61; &#123;&#125;&#10; &#34;1_Sensitive&#34; &#61; &#123;&#125;&#10;&#125;">&#123;&#8230;&#125;</code> | | [data_catalog_tags](variables.tf#L105) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code title="&#123;&#10; &#34;3_Confidential&#34; &#61; &#123;&#125;&#10; &#34;2_Private&#34; &#61; &#123;&#125;&#10; &#34;1_Sensitive&#34; &#61; &#123;&#125;&#10;&#125;">&#123;&#8230;&#125;</code> |
| [data_force_destroy](variables.tf#L114) | Flag to set 'force_destroy' on data services like BiguQery or Cloud Storage. | <code>bool</code> | | <code>false</code> | | [data_force_destroy](variables.tf#L119) | Flag to set 'force_destroy' on data services like BiguQery or Cloud Storage. | <code>bool</code> | | <code>false</code> |
| [groups](variables.tf#L120) | User groups. | <code>map&#40;string&#41;</code> | | <code title="&#123;&#10; data-analysts &#61; &#34;gcp-data-analysts&#34;&#10; data-engineers &#61; &#34;gcp-data-engineers&#34;&#10; data-security &#61; &#34;gcp-data-security&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | | [groups](variables.tf#L125) | User groups. | <code>map&#40;string&#41;</code> | | <code title="&#123;&#10; data-analysts &#61; &#34;gcp-data-analysts&#34;&#10; data-engineers &#61; &#34;gcp-data-engineers&#34;&#10; data-security &#61; &#34;gcp-data-security&#34;&#10;&#125;">&#123;&#8230;&#125;</code> |
| [location](variables.tf#L130) | Location used for multi-regional resources. | <code>string</code> | | <code>&#34;eu&#34;</code> | | [location](variables.tf#L135) | Location used for multi-regional resources. | <code>string</code> | | <code>&#34;eu&#34;</code> |
| [network_config](variables.tf#L136) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; network_self_link &#61; string&#10; subnet_self_links &#61; object&#40;&#123;&#10; load &#61; string&#10; transformation &#61; string&#10; orchestration &#61; string&#10; &#125;&#41;&#10; composer_ip_ranges &#61; object&#40;&#123;&#10; cloudsql &#61; string&#10; gke_master &#61; string&#10; &#125;&#41;&#10; composer_secondary_ranges &#61; object&#40;&#123;&#10; pods &#61; string&#10; services &#61; string&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [network_config](variables.tf#L141) | Shared VPC network configurations to use. If null networks will be created in projects with preconfigured values. | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; network_self_link &#61; string&#10; subnet_self_links &#61; object&#40;&#123;&#10; load &#61; string&#10; transformation &#61; string&#10; orchestration &#61; string&#10; &#125;&#41;&#10; composer_ip_ranges &#61; object&#40;&#123;&#10; cloudsql &#61; string&#10; gke_master &#61; string&#10; &#125;&#41;&#10; composer_secondary_ranges &#61; object&#40;&#123;&#10; pods &#61; string&#10; services &#61; string&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [project_services](variables.tf#L207) | List of core services enabled on all projects. | <code>list&#40;string&#41;</code> | | <code title="&#91;&#10; &#34;cloudresourcemanager.googleapis.com&#34;,&#10; &#34;iam.googleapis.com&#34;,&#10; &#34;serviceusage.googleapis.com&#34;,&#10; &#34;stackdriver.googleapis.com&#34;&#10;&#93;">&#91;&#8230;&#93;</code> | | [project_services](variables.tf#L212) | List of core services enabled on all projects. | <code>list&#40;string&#41;</code> | | <code title="&#91;&#10; &#34;cloudresourcemanager.googleapis.com&#34;,&#10; &#34;iam.googleapis.com&#34;,&#10; &#34;serviceusage.googleapis.com&#34;,&#10; &#34;stackdriver.googleapis.com&#34;&#10;&#93;">&#91;&#8230;&#93;</code> |
| [project_suffix](variables.tf#L218) | Suffix used only for project ids. | <code>string</code> | | <code>null</code> | | [project_suffix](variables.tf#L223) | Suffix used only for project ids. | <code>string</code> | | <code>null</code> |
| [region](variables.tf#L224) | Region used for regional resources. | <code>string</code> | | <code>&#34;europe-west1&#34;</code> | | [region](variables.tf#L229) | Region used for regional resources. | <code>string</code> | | <code>&#34;europe-west1&#34;</code> |
| [service_encryption_keys](variables.tf#L230) | Cloud KMS to use to encrypt different services. Key location should match service region. | <code title="object&#40;&#123;&#10; bq &#61; string&#10; composer &#61; string&#10; dataflow &#61; string&#10; storage &#61; string&#10; pubsub &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [service_encryption_keys](variables.tf#L235) | Cloud KMS to use to encrypt different services. Key location should match service region. | <code title="object&#40;&#123;&#10; bq &#61; string&#10; composer &#61; string&#10; dataflow &#61; string&#10; storage &#61; string&#10; pubsub &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
## Outputs ## Outputs
@ -296,18 +294,3 @@ Features to add in future releases:
- Add example on how to use Cloud Data Loss Prevention - Add example on how to use Cloud Data Loss Prevention
- Add solution to handle Tables, Views, and Authorized Views lifecycle - Add solution to handle Tables, Views, and Authorized Views lifecycle
- Add solution to handle Metadata lifecycle - Add solution to handle Metadata lifecycle
## Test
```hcl
module "test" {
source = "./fabric/blueprints/data-solutions/data-platform-foundations/"
organization_domain = "example.com"
project_config = {
billing_account_id = "123456-123456-123456"
parent = "folders/12345678"
}
prefix = "prefix"
}
# tftest modules=43 resources=285
```

View File

@ -0,0 +1,37 @@
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
locals {
_drp_iam = flatten([
for principal, roles in local.drp_iam : [
for role in roles : {
key = "${principal}-${role}"
principal = principal
role = role
}
]
])
drp_iam_additive = {
for binding in local._drp_iam : binding.key => {
role = binding.role
member = local.iam_principals[binding.principal]
}
}
drp_iam_auth = {
for binding in local._drp_iam :
binding.role => local.iam_principals[binding.principal]...
}
}

View File

@ -0,0 +1,47 @@
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
locals {
_load_iam = flatten([
for principal, roles in local.load_iam : [
for role in roles : {
key = "${principal}-${role}"
principal = principal
role = role
}
]
])
load_iam_additive = {
for binding in local._load_iam : binding.key => {
role = binding.role
member = local.iam_principals[binding.principal]
}
}
load_iam_auth = {
for binding in local._load_iam :
binding.role => local.iam_principals[binding.principal]...
}
load_subnet = (
local.use_shared_vpc
? var.network_config.subnet_self_links.orchestration
: values(module.load-vpc.0.subnet_self_links)[0]
)
load_vpc = (
local.use_shared_vpc
? var.network_config.network_self_link
: module.load-vpc.0.self_link
)
}

View File

@ -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.
*/
locals {
_orch_iam = flatten([
for principal, roles in local.orch_iam : [
for role in roles : {
key = "${principal}-${role}"
principal = principal
role = role
}
]
])
orch_iam_additive = {
for binding in local._orch_iam : binding.key => {
role = binding.role
member = local.iam_principals[binding.principal]
}
}
orch_iam_auth = {
for binding in local._orch_iam :
binding.role => local.iam_principals[binding.principal]...
}
orch_subnet = (
local.use_shared_vpc
? var.network_config.subnet_self_links.orchestration
: values(module.orch-vpc.0.subnet_self_links)[0]
)
orch_vpc = (
local.use_shared_vpc
? var.network_config.network_self_link
: module.orch-vpc.0.self_link
)
# TODO: use new artifact registry module output
orch_docker_path = format("%s-docker.pkg.dev/%s/%s",
var.region, module.orch-project.project_id, module.orch-artifact-reg.name)
}

View File

@ -0,0 +1,47 @@
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
locals {
_trf_iam = flatten([
for principal, roles in local.trf_iam : [
for role in roles : {
key = "${principal}-${role}"
principal = principal
role = role
}
]
])
trf_iam_additive = {
for binding in local._trf_iam : binding.key => {
role = binding.role
member = local.iam_principals[binding.principal]
}
}
trf_iam_auth = {
for binding in local._trf_iam :
binding.role => local.iam_principals[binding.principal]...
}
transf_subnet = (
local.use_shared_vpc
? var.network_config.subnet_self_links.orchestration
: values(module.transf-vpc.0.subnet_self_links)[0]
)
transf_vpc = (
local.use_shared_vpc
? var.network_config.network_self_link
: module.transf-vpc.0.self_link
)
}

View File

@ -0,0 +1,68 @@
/**
* 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 {
_dwh_iam = flatten([
for principal, roles in local.dwh_iam : [
for role in roles : {
key = "${principal}-${role}"
principal = principal
role = role
}
]
])
_lnd_iam = flatten([
for principal, roles in local.lnd_iam : [
for role in roles : {
key = "${principal}-${role}"
principal = principal
role = role
}
]
])
dwh_iam_additive = {
for binding in local._dwh_iam : binding.key => {
role = binding.role
member = local.iam_principals[binding.principal]
}
}
dwh_iam_auth = {
for binding in local._dwh_iam :
binding.role => local.iam_principals[binding.principal]...
}
dwh_services = concat(var.project_services, [
"bigquery.googleapis.com",
"bigqueryreservation.googleapis.com",
"bigquerystorage.googleapis.com",
"cloudkms.googleapis.com",
"compute.googleapis.com",
"dataflow.googleapis.com",
"pubsub.googleapis.com",
"servicenetworking.googleapis.com",
"storage.googleapis.com",
"storage-component.googleapis.com"
])
lnd_iam_additive = {
for binding in local._lnd_iam : binding.key => {
role = binding.role
member = local.iam_principals[binding.principal]
}
}
lnd_iam_auth = {
for binding in local._lnd_iam :
binding.role => local.iam_principals[binding.principal]...
}
}

View File

@ -0,0 +1,37 @@
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
locals {
_cmn_iam = flatten([
for principal, roles in local.cmn_iam : [
for role in roles : {
key = "${principal}-${role}"
principal = principal
role = role
}
]
])
cmn_iam_additive = {
for binding in local._cmn_iam : binding.key => {
role = binding.role
member = local.iam_principals[binding.principal]
}
}
cmn_iam_auth = {
for binding in local._cmn_iam :
binding.role => local.iam_principals[binding.principal]...
}
}

View File

@ -35,6 +35,22 @@ locals {
groups_iam = { groups_iam = {
for k, v in local.groups : k => "group:${v}" for k, v in local.groups : k => "group:${v}"
} }
iam_principals = {
data_analysts = "group:${local.groups.data-analysts}"
data_engineers = "group:${local.groups.data-engineers}"
data_security = "group:${local.groups.data-security}"
robots_cloudbuild = "serviceAccount:${module.orch-project.service_accounts.robots.cloudbuild}"
robots_composer = "serviceAccount:${module.orch-project.service_accounts.robots.composer}"
robots_dataflow_load = "serviceAccount:${module.load-project.service_accounts.robots.dataflow}"
robots_dataflow_trf = "serviceAccount:${module.transf-project.service_accounts.robots.dataflow}"
sa_drop_bq = module.drop-sa-bq-0.iam_email
sa_drop_cs = module.drop-sa-cs-0.iam_email
sa_drop_ps = module.drop-sa-ps-0.iam_email
sa_load = module.load-sa-df-0.iam_email
sa_orch = module.orch-sa-cmp-0.iam_email
sa_transf_bq = module.transf-sa-bq-0.iam_email,
sa_transf_df = module.transf-sa-df-0.iam_email,
}
project_suffix = var.project_suffix == null ? "" : "-${var.project_suffix}" project_suffix = var.project_suffix == null ? "" : "-${var.project_suffix}"
service_encryption_keys = var.service_encryption_keys service_encryption_keys = var.service_encryption_keys
shared_vpc_project = try(var.network_config.host_project, null) shared_vpc_project = try(var.network_config.host_project, null)
@ -57,6 +73,7 @@ locals {
] ]
]) : "${binding.role}-${binding.member}" => binding ]) : "${binding.role}-${binding.member}" => binding
} }
use_projects = var.project_config.billing_account_id == null
use_shared_vpc = var.network_config != null use_shared_vpc = var.network_config != null
} }

View File

@ -19,54 +19,59 @@ variable "composer_config" {
type = object({ type = object({
disable_deployment = optional(bool) disable_deployment = optional(bool)
environment_size = optional(string, "ENVIRONMENT_SIZE_SMALL") environment_size = optional(string, "ENVIRONMENT_SIZE_SMALL")
software_config = optional(object({ software_config = optional(
airflow_config_overrides = optional(any) object({
pypi_packages = optional(any) airflow_config_overrides = optional(any)
env_variables = optional(map(string)) pypi_packages = optional(any)
image_version = string env_variables = optional(map(string))
}), { image_version = string
image_version = "composer-2-airflow-2" }),
}) { image_version = "composer-2-airflow-2" }
workloads_config = optional(object({ )
scheduler = optional(object( workloads_config = optional(
{ object({
cpu = number scheduler = optional(
memory_gb = number object({
storage_gb = number cpu = number
count = number memory_gb = number
} storage_gb = number
), { count = number
cpu = 0.5 }),
memory_gb = 1.875 {
storage_gb = 1 cpu = 0.5
count = 1 memory_gb = 1.875
}) storage_gb = 1
web_server = optional(object( count = 1
{ }
cpu = number )
memory_gb = number web_server = optional(
storage_gb = number object({
} cpu = number
), { memory_gb = number
cpu = 0.5 storage_gb = number
memory_gb = 1.875 }),
storage_gb = 1 {
}) cpu = 0.5
worker = optional(object( memory_gb = 1.875
{ storage_gb = 1
cpu = number }
memory_gb = number )
storage_gb = number worker = optional(
min_count = number object({
max_count = number cpu = number
} memory_gb = number
), { storage_gb = number
cpu = 0.5 min_count = number
memory_gb = 1.875 max_count = number
storage_gb = 1 }),
min_count = 1 {
max_count = 3 cpu = 0.5
}) memory_gb = 1.875
storage_gb = 1
min_count = 1
max_count = 3
}
)
})) }))
}) })
default = { default = {

View File

@ -16,9 +16,26 @@
locals { locals {
iam_lnd = { iam_lnd = {
"roles/storage.objectCreator" = [module.land-sa-0.iam_email] "roles/storage.objectCreator" = [
"roles/storage.objectViewer" = [module.processing-sa-cmp-0.iam_email] module.land-sa-0.iam_email
"roles/storage.objectAdmin" = [module.processing-sa-0.iam_email] ]
"roles/storage.objectViewer" = [
module.processing-sa-cmp-0.iam_email
]
"roles/storage.objectAdmin" = [
module.processing-sa-0.iam_email
]
}
# this only works because the service account module uses a static output
iam_lnd_additive = {
for k in flatten([
for role, members in local.iam_lnd : [
for member in members : {
role = role
member = member
}
]
]) : "${k.member}-${k.role}" => k
} }
} }
@ -27,14 +44,20 @@ module "land-project" {
parent = var.project_config.parent parent = var.project_config.parent
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null project_create = var.project_config.billing_account_id != null
prefix = var.project_config.billing_account_id == null ? null : var.prefix prefix = (
var.project_config.billing_account_id == null ? null : var.prefix
)
name = ( name = (
var.project_config.billing_account_id == null var.project_config.billing_account_id == null
? var.project_config.project_ids.landing ? var.project_config.project_ids.landing
: "${var.project_config.project_ids.landing}${local.project_suffix}" : "${var.project_config.project_ids.landing}${local.project_suffix}"
) )
iam = var.project_config.billing_account_id != null ? local.iam_lnd : null iam = (
iam_additive = var.project_config.billing_account_id == null ? local.iam_lnd : null var.project_config.billing_account_id == null ? {} : local.iam_lnd
)
iam_bindings_additive = (
var.project_config.billing_account_id != null ? {} : local.iam_lnd_additive
)
services = [ services = [
"bigquery.googleapis.com", "bigquery.googleapis.com",
"bigqueryreservation.googleapis.com", "bigqueryreservation.googleapis.com",

View File

@ -15,15 +15,23 @@
# tfdoc:file:description Processing project and VPC. # tfdoc:file:description Processing project and VPC.
locals { locals {
iam_processing = { iam_prc = {
"roles/bigquery.jobUser" = [ "roles/bigquery.jobUser" = [
module.processing-sa-cmp-0.iam_email, module.processing-sa-cmp-0.iam_email,
module.processing-sa-0.iam_email module.processing-sa-0.iam_email
] ]
"roles/composer.admin" = [local.groups_iam.data-engineers] "roles/composer.admin" = [
"roles/dataflow.admin" = [module.processing-sa-cmp-0.iam_email] local.groups_iam.data-engineers
"roles/dataflow.worker" = [module.processing-sa-0.iam_email] ]
"roles/composer.environmentAndStorageObjectAdmin" = [local.groups_iam.data-engineers] "roles/dataflow.admin" = [
module.processing-sa-cmp-0.iam_email
]
"roles/dataflow.worker" = [
module.processing-sa-0.iam_email
]
"roles/composer.environmentAndStorageObjectAdmin" = [
local.groups_iam.data-engineers
]
"roles/composer.ServiceAgentV2Ext" = [ "roles/composer.ServiceAgentV2Ext" = [
"serviceAccount:${module.processing-project.service_accounts.robots.composer}" "serviceAccount:${module.processing-project.service_accounts.robots.composer}"
] ]
@ -37,20 +45,39 @@ locals {
module.processing-sa-0.iam_email module.processing-sa-0.iam_email
] ]
"roles/iam.serviceAccountUser" = [ "roles/iam.serviceAccountUser" = [
module.processing-sa-cmp-0.iam_email, local.groups_iam.data-engineers module.processing-sa-cmp-0.iam_email,
local.groups_iam.data-engineers
]
"roles/iap.httpsResourceAccessor" = [
local.groups_iam.data-engineers
]
"roles/serviceusage.serviceUsageConsumer" = [
local.groups_iam.data-engineers
] ]
"roles/iap.httpsResourceAccessor" = [local.groups_iam.data-engineers]
"roles/serviceusage.serviceUsageConsumer" = [local.groups_iam.data-engineers]
"roles/storage.admin" = [ "roles/storage.admin" = [
module.processing-sa-cmp-0.iam_email, module.processing-sa-cmp-0.iam_email,
"serviceAccount:${module.processing-project.service_accounts.robots.composer}", "serviceAccount:${module.processing-project.service_accounts.robots.composer}",
local.groups_iam.data-engineers local.groups_iam.data-engineers
] ]
} }
# this only works because the service account module uses a static output
iam_prc_additive = {
for k in flatten([
for role, members in local.iam_prc : [
for member in members : {
role = role
member = member
}
]
]) : "${k.member}-${k.role}" => k
}
processing_subnet = ( processing_subnet = (
local.use_shared_vpc local.use_shared_vpc
? var.network_config.subnet_self_link ? var.network_config.subnet_self_link
: try(module.processing-vpc.0.subnet_self_links["${var.region}/${var.prefix}-processing"], null) : try(
module.processing-vpc.0.subnet_self_links["${var.region}/${var.prefix}-processing"],
null
)
) )
processing_vpc = ( processing_vpc = (
local.use_shared_vpc local.use_shared_vpc
@ -64,15 +91,23 @@ module "processing-project" {
parent = var.project_config.parent parent = var.project_config.parent
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null project_create = var.project_config.billing_account_id != null
prefix = var.project_config.billing_account_id == null ? null : var.prefix prefix = (
var.project_config.billing_account_id == null ? null : var.prefix
)
name = ( name = (
var.project_config.billing_account_id == null var.project_config.billing_account_id == null
? var.project_config.project_ids.processing ? var.project_config.project_ids.processing
: "${var.project_config.project_ids.processing}${local.project_suffix}" : "${var.project_config.project_ids.processing}${local.project_suffix}"
) )
iam = var.project_config.billing_account_id != null ? local.iam_processing : null iam = (
iam_additive = var.project_config.billing_account_id == null ? local.iam_processing : null var.project_config.billing_account_id == null ? {} : local.iam_prc
oslogin = false )
iam_bindings_additive = (
var.project_config.billing_account_id != null ? {} : local.iam_prc_additive
)
compute_metadata = {
enable-oslogin = "false"
}
services = [ services = [
"bigquery.googleapis.com", "bigquery.googleapis.com",
"bigqueryreservation.googleapis.com", "bigqueryreservation.googleapis.com",

View File

@ -15,15 +15,32 @@
# tfdoc:file:description Data curated project and resources. # tfdoc:file:description Data curated project and resources.
locals { locals {
cur_iam = { cur_services = [
"roles/bigquery.dataOwner" = [module.processing-sa-0.iam_email] "bigquery.googleapis.com",
"bigqueryreservation.googleapis.com",
"bigquerystorage.googleapis.com",
"cloudkms.googleapis.com",
"cloudresourcemanager.googleapis.com",
"compute.googleapis.com",
"iam.googleapis.com",
"servicenetworking.googleapis.com",
"serviceusage.googleapis.com",
"stackdriver.googleapis.com",
"storage.googleapis.com",
"storage-component.googleapis.com"
]
iam_cur = {
"roles/bigquery.dataOwner" = [
module.processing-sa-0.iam_email
]
"roles/bigquery.dataViewer" = [ "roles/bigquery.dataViewer" = [
module.cur-sa-0.iam_email, module.cur-sa-0.iam_email,
local.groups_iam.data-analysts, local.groups_iam.data-analysts,
local.groups_iam.data-engineers local.groups_iam.data-engineers
] ]
"roles/bigquery.jobUser" = [ "roles/bigquery.jobUser" = [
module.processing-sa-0.iam_email, # Remove once bug is fixed. https://github.com/apache/airflow/issues/32106 # Remove once bug is fixed. https://github.com/apache/airflow/issues/32106
module.processing-sa-0.iam_email,
module.cur-sa-0.iam_email, module.cur-sa-0.iam_email,
local.groups_iam.data-analysts, local.groups_iam.data-analysts,
local.groups_iam.data-engineers local.groups_iam.data-engineers
@ -43,22 +60,21 @@ locals {
local.groups_iam.data-analysts, local.groups_iam.data-analysts,
local.groups_iam.data-engineers local.groups_iam.data-engineers
] ]
"roles/storage.objectAdmin" = [module.processing-sa-0.iam_email] "roles/storage.objectAdmin" = [
module.processing-sa-0.iam_email
]
}
# this only works because the service account module uses a static output
iam_cur_additive = {
for k in flatten([
for role, members in local.iam_cur : [
for member in members : {
role = role
member = member
}
]
]) : "${k.member}-${k.role}" => k
} }
cur_services = [
"bigquery.googleapis.com",
"bigqueryreservation.googleapis.com",
"bigquerystorage.googleapis.com",
"cloudkms.googleapis.com",
"cloudresourcemanager.googleapis.com",
"compute.googleapis.com",
"iam.googleapis.com",
"servicenetworking.googleapis.com",
"serviceusage.googleapis.com",
"stackdriver.googleapis.com",
"storage.googleapis.com",
"storage-component.googleapis.com"
]
} }
# Project # Project
@ -68,15 +84,21 @@ module "cur-project" {
parent = var.project_config.parent parent = var.project_config.parent
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null project_create = var.project_config.billing_account_id != null
prefix = var.project_config.billing_account_id == null ? null : var.prefix prefix = (
var.project_config.billing_account_id == null ? null : var.prefix
)
name = ( name = (
var.project_config.billing_account_id == null var.project_config.billing_account_id == null
? var.project_config.project_ids.curated ? var.project_config.project_ids.curated
: "${var.project_config.project_ids.curated}${local.project_suffix}" : "${var.project_config.project_ids.curated}${local.project_suffix}"
) )
iam = var.project_config.billing_account_id != null ? local.cur_iam : {} iam = (
iam_additive = var.project_config.billing_account_id == null ? local.cur_iam : {} var.project_config.billing_account_id != null ? {} : local.iam_cur
services = local.cur_services )
iam_bindings_additive = (
var.project_config.billing_account_id == null ? {} : local.iam_cur_additive
)
services = local.cur_services
service_encryption_key_ids = { service_encryption_key_ids = {
bq = [var.service_encryption_keys.bq] bq = [var.service_encryption_keys.bq]
storage = [var.service_encryption_keys.storage] storage = [var.service_encryption_keys.storage]

View File

@ -15,15 +15,23 @@
# tfdoc:file:description Common project and resources. # tfdoc:file:description Common project and resources.
locals { locals {
iam_common = { iam_cmn = {
"roles/dlp.admin" = [local.groups_iam.data-security] "roles/dlp.admin" = [
"roles/dlp.estimatesAdmin" = [local.groups_iam.data-engineers] local.groups_iam.data-security
"roles/dlp.reader" = [local.groups_iam.data-engineers] ]
"roles/dlp.estimatesAdmin" = [
local.groups_iam.data-engineers
]
"roles/dlp.reader" = [
local.groups_iam.data-engineers
]
"roles/dlp.user" = [ "roles/dlp.user" = [
module.processing-sa-0.iam_email, module.processing-sa-0.iam_email,
local.groups_iam.data-engineers local.groups_iam.data-engineers
] ]
"roles/datacatalog.admin" = [local.groups_iam.data-security] "roles/datacatalog.admin" = [
local.groups_iam.data-security
]
"roles/datacatalog.viewer" = [ "roles/datacatalog.viewer" = [
module.processing-sa-0.iam_email, module.processing-sa-0.iam_email,
local.groups_iam.data-analysts local.groups_iam.data-analysts
@ -32,20 +40,37 @@ locals {
module.processing-sa-0.iam_email module.processing-sa-0.iam_email
] ]
} }
# this only works because the service account module uses a static output
iam_cmn_additive = {
for k in flatten([
for role, members in local.iam_cmn : [
for member in members : {
role = role
member = member
}
]
]) : "${k.member}-${k.role}" => k
}
} }
module "common-project" { module "common-project" {
source = "../../../modules/project" source = "../../../modules/project"
parent = var.project_config.parent parent = var.project_config.parent
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null project_create = var.project_config.billing_account_id != null
prefix = var.project_config.billing_account_id == null ? null : var.prefix prefix = (
var.project_config.billing_account_id == null ? null : var.prefix
)
name = ( name = (
var.project_config.billing_account_id == null var.project_config.billing_account_id == null
? var.project_config.project_ids.common ? var.project_config.project_ids.common
: "${var.project_config.project_ids.common}${local.project_suffix}" : "${var.project_config.project_ids.common}${local.project_suffix}"
) )
iam = var.project_config.billing_account_id != null ? local.iam_common : null iam = (
iam_additive = var.project_config.billing_account_id == null ? local.iam_common : null var.project_config.billing_account_id == null ? {} : local.iam_cmn
)
iam_bindings_additive = (
var.project_config.billing_account_id != null ? {} : local.iam_cmn_additive
)
services = [ services = [
"cloudresourcemanager.googleapis.com", "cloudresourcemanager.googleapis.com",
"datacatalog.googleapis.com", "datacatalog.googleapis.com",

View File

@ -12,6 +12,32 @@ The following diagram is a high-level reference of the resources created and man
A set of demo [Airflow pipelines](./demo/) are also part of this blueprint: they can be run on top of the foundational infrastructure to verify and test the setup. A set of demo [Airflow pipelines](./demo/) are also part of this blueprint: they can be run on top of the foundational infrastructure to verify and test the setup.
<!-- BEGIN TOC -->
- [Design overview and choices](#design-overview-and-choices)
- [Project structure](#project-structure)
- [Roles](#roles)
- [Service accounts](#service-accounts)
- [User groups](#user-groups)
- [Virtual Private Cloud (VPC) design](#virtual-private-cloud-vpc-design)
- [IP ranges and subnetting](#ip-ranges-and-subnetting)
- [Resource naming conventions](#resource-naming-conventions)
- [Encryption](#encryption)
- [Data Anonymization](#data-anonymization)
- [Data Catalog](#data-catalog)
- [How to run this script](#how-to-run-this-script)
- [Variable configuration](#variable-configuration)
- [How to use this blueprint from Terraform](#how-to-use-this-blueprint-from-terraform)
- [Customizations](#customizations)
- [Assign roles at BQ Dataset level](#assign-roles-at-bq-dataset-level)
- [Project Configuration](#project-configuration)
- [Shared VPC](#shared-vpc)
- [Customer Managed Encryption key](#customer-managed-encryption-key)
- [Demo pipeline](#demo-pipeline)
- [Files](#files)
- [Variables](#variables)
- [Outputs](#outputs)
<!-- END TOC -->
## Design overview and choices ## Design overview and choices
Despite its simplicity, this stage implements the basics of a design that we've seen working well for various customers. Despite its simplicity, this stage implements the basics of a design that we've seen working well for various customers.
@ -203,7 +229,7 @@ module "data-platform" {
prefix = "myprefix" prefix = "myprefix"
} }
# tftest modules=23 resources=123 # tftest modules=23 resources=135
``` ```
## Customizations ## Customizations

View File

@ -24,7 +24,7 @@ If the network_config variable is not provided, one VPC will be created in each
## Deploy your environment ## Deploy your environment
We assume the identiy running the following steps has the following role: We assume the identity running the following steps has the following role:
- resourcemanager.projectCreator in case a new project will be created. - resourcemanager.projectCreator in case a new project will be created.
- owner on the project in case you use an existing project. - owner on the project in case you use an existing project.

View File

@ -14,16 +14,6 @@
locals { locals {
iam = { iam = {
"roles/iam.serviceAccountUser" = [
module.service-account-orch.iam_email
]
"roles/iam.serviceAccountTokenCreator" = var.data_eng_principals
# GCS roles
"roles/storage.objectAdmin" = [
module.service-account-df.iam_email,
module.service-account-landing.iam_email
]
# BigQuery roles
"roles/bigquery.admin" = var.data_eng_principals "roles/bigquery.admin" = var.data_eng_principals
"roles/bigquery.dataOwner" = [ "roles/bigquery.dataOwner" = [
module.service-account-df.iam_email module.service-account-df.iam_email
@ -34,9 +24,7 @@ locals {
"roles/bigquery.jobUser" = [ "roles/bigquery.jobUser" = [
module.service-account-bq.iam_email module.service-account-bq.iam_email
] ]
# Compute
"roles/compute.viewer" = var.data_eng_principals "roles/compute.viewer" = var.data_eng_principals
# Dataflow roles
"roles/dataflow.admin" = concat( "roles/dataflow.admin" = concat(
[module.service-account-orch.iam_email], [module.service-account-orch.iam_email],
var.data_eng_principals var.data_eng_principals
@ -45,6 +33,25 @@ locals {
"roles/dataflow.worker" = [ "roles/dataflow.worker" = [
module.service-account-df.iam_email, module.service-account-df.iam_email,
] ]
"roles/iam.serviceAccountUser" = [
module.service-account-orch.iam_email
]
"roles/iam.serviceAccountTokenCreator" = var.data_eng_principals
"roles/storage.objectAdmin" = [
module.service-account-df.iam_email,
module.service-account-landing.iam_email
]
}
# this only works because the service account module uses a static output
iam_additive = {
for k in flatten([
for role, members in local.iam : [
for member in members : {
role = role
member = member
}
]
]) : "${k.member}-${k.role}" => k
} }
network_subnet_selflink = try( network_subnet_selflink = try(
module.vpc[0].subnets["${var.region}/subnet"].self_link, module.vpc[0].subnets["${var.region}/subnet"].self_link,
@ -75,8 +82,12 @@ module "project" {
"storage.googleapis.com", "storage.googleapis.com",
"storage-component.googleapis.com", "storage-component.googleapis.com",
] ]
iam = var.project_config.billing_account_id != null ? local.iam : {} iam = (
iam_additive = var.project_config.billing_account_id == null ? local.iam : {} var.project_config.billing_account_id != null ? local.iam : {}
)
iam_bindings_additive = (
var.project_config.billing_account_id == null ? local.iam_additive : {}
)
shared_vpc_service_config = var.network_config.host_project == null ? null : { shared_vpc_service_config = var.network_config.host_project == null ? null : {
attach = true attach = true
host_project = var.network_config.host_project host_project = var.network_config.host_project

View File

@ -153,25 +153,24 @@ terraform init
terraform apply terraform apply
``` ```
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Variables ## Variables
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [access_policy_config](variables.tf#L17) | Provide 'access_policy_create' values if a folder scoped Access Policy creation is needed, uses existing 'policy_name' otherwise. Parent is in 'organizations/123456' format. Policy will be created scoped to the folder. | <code title="object&#40;&#123;&#10; policy_name &#61; optional&#40;string, null&#41;&#10; access_policy_create &#61; optional&#40;object&#40;&#123;&#10; parent &#61; string&#10; title &#61; string&#10; &#125;&#41;, null&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | [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. | <code title="object&#40;&#123;&#10; policy_name &#61; optional&#40;string, null&#41;&#10; access_policy_create &#61; optional&#40;object&#40;&#123;&#10; parent &#61; string&#10; title &#61; string&#10; &#125;&#41;, null&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [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. | <code title="object&#40;&#123;&#10; folder_id &#61; optional&#40;string, null&#41;&#10; folder_create &#61; optional&#40;object&#40;&#123;&#10; display_name &#61; string&#10; parent &#61; string&#10; &#125;&#41;, null&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | [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. | <code title="object&#40;&#123;&#10; folder_id &#61; optional&#40;string, null&#41;&#10; folder_create &#61; optional&#40;object&#40;&#123;&#10; display_name &#61; string&#10; parent &#61; string&#10; &#125;&#41;, null&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [organization](variables.tf#L128) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | [organization](variables.tf#L129) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [prefix](variables.tf#L136) | Prefix used for resources that need unique names. | <code>string</code> | ✓ | | | [prefix](variables.tf#L137) | Prefix used for resources that need unique names. | <code>string</code> | ✓ | |
| [project_config](variables.tf#L141) | Provide 'billing_account_id' value if project creation is needed, uses existing 'project_ids' if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | <code title="object&#40;&#123;&#10; billing_account_id &#61; optional&#40;string, null&#41;&#10; project_ids &#61; optional&#40;object&#40;&#123;&#10; sec-core &#61; string&#10; audit-logs &#61; string&#10; &#125;&#41;, &#123;&#10; sec-core &#61; &#34;sec-core&#34;&#10; audit-logs &#61; &#34;audit-logs&#34;&#10; &#125;&#10; &#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | [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. | <code title="object&#40;&#123;&#10; billing_account_id &#61; optional&#40;string, null&#41;&#10; project_ids &#61; optional&#40;object&#40;&#123;&#10; sec-core &#61; string&#10; audit-logs &#61; string&#10; &#125;&#41;, &#123;&#10; sec-core &#61; &#34;sec-core&#34;&#10; audit-logs &#61; &#34;audit-logs&#34;&#10; &#125;&#10; &#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [data_dir](variables.tf#L29) | Relative path for the folder storing configuration data. | <code>string</code> | | <code>&#34;data&#34;</code> | | [data_dir](variables.tf#L29) | Relative path for the folder storing configuration data. | <code>string</code> | | <code>&#34;data&#34;</code> |
| [enable_features](variables.tf#L35) | Flag to enable features on the solution. | <code title="object&#40;&#123;&#10; encryption &#61; optional&#40;bool, false&#41;&#10; log_sink &#61; optional&#40;bool, true&#41;&#10; vpc_sc &#61; optional&#40;bool, true&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; encryption &#61; false&#10; log_sink &#61; true&#10; vpc_sc &#61; true&#10;&#125;">&#123;&#8230;&#125;</code> | | [enable_features](variables.tf#L35) | Flag to enable features on the solution. | <code title="object&#40;&#123;&#10; encryption &#61; optional&#40;bool, false&#41;&#10; log_sink &#61; optional&#40;bool, true&#41;&#10; vpc_sc &#61; optional&#40;bool, true&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; encryption &#61; false&#10; log_sink &#61; true&#10; vpc_sc &#61; true&#10;&#125;">&#123;&#8230;&#125;</code> |
| [groups](variables.tf#L65) | User groups. | <code title="object&#40;&#123;&#10; workload-engineers &#61; optional&#40;string, &#34;gcp-data-engineers&#34;&#41;&#10; workload-security &#61; optional&#40;string, &#34;gcp-data-security&#34;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | | [groups](variables.tf#L65) | User groups. | <code title="object&#40;&#123;&#10; workload-engineers &#61; optional&#40;string, &#34;gcp-data-engineers&#34;&#41;&#10; workload-security &#61; optional&#40;string, &#34;gcp-data-security&#34;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [kms_keys](variables.tf#L75) | KMS keys to create, keyed by name. | <code title="map&#40;object&#40;&#123;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; labels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; locations &#61; optional&#40;list&#40;string&#41;, &#91;&#34;global&#34;, &#34;europe&#34;, &#34;europe-west1&#34;&#93;&#41;&#10; rotation_period &#61; optional&#40;string, &#34;7776000s&#34;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [kms_keys](variables.tf#L75) | KMS keys to create, keyed by name. | <code title="map&#40;object&#40;&#123;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; iam_bindings_additive &#61; optional&#40;map&#40;map&#40;any&#41;&#41;, &#123;&#125;&#41;&#10; labels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; locations &#61; optional&#40;list&#40;string&#41;, &#91;&#34;global&#34;, &#34;europe&#34;, &#34;europe-west1&#34;&#93;&#41;&#10; rotation_period &#61; optional&#40;string, &#34;7776000s&#34;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [log_locations](variables.tf#L86) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object&#40;&#123;&#10; bq &#61; optional&#40;string, &#34;europe&#34;&#41;&#10; storage &#61; optional&#40;string, &#34;europe&#34;&#41;&#10; logging &#61; optional&#40;string, &#34;global&#34;&#41;&#10; pubsub &#61; optional&#40;string, &#34;global&#34;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; bq &#61; &#34;europe&#34;&#10; storage &#61; &#34;europe&#34;&#10; logging &#61; &#34;global&#34;&#10; pubsub &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> | | [log_locations](variables.tf#L87) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object&#40;&#123;&#10; bq &#61; optional&#40;string, &#34;europe&#34;&#41;&#10; storage &#61; optional&#40;string, &#34;europe&#34;&#41;&#10; logging &#61; optional&#40;string, &#34;global&#34;&#41;&#10; pubsub &#61; optional&#40;string, &#34;global&#34;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; bq &#61; &#34;europe&#34;&#10; storage &#61; &#34;europe&#34;&#10; logging &#61; &#34;global&#34;&#10; pubsub &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> |
| [log_sinks](variables.tf#L103) | Org-level log sinks, in name => {type, filter} format. | <code title="map&#40;object&#40;&#123;&#10; filter &#61; string&#10; type &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code title="&#123;&#10; audit-logs &#61; &#123;&#10; filter &#61; &#34;logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Factivity&#92;&#34; OR logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Fsystem_event&#92;&#34;&#34;&#10; type &#61; &#34;bigquery&#34;&#10; &#125;&#10; vpc-sc &#61; &#123;&#10; filter &#61; &#34;protoPayload.metadata.&#64;type&#61;&#92;&#34;type.googleapis.com&#47;google.cloud.audit.VpcServiceControlAuditMetadata&#92;&#34;&#34;&#10; type &#61; &#34;bigquery&#34;&#10; &#125;&#10;&#125;">&#123;&#8230;&#125;</code> | | [log_sinks](variables.tf#L104) | Org-level log sinks, in name => {type, filter} format. | <code title="map&#40;object&#40;&#123;&#10; filter &#61; string&#10; type &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code title="&#123;&#10; audit-logs &#61; &#123;&#10; filter &#61; &#34;logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Factivity&#92;&#34; OR logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Fsystem_event&#92;&#34;&#34;&#10; type &#61; &#34;bigquery&#34;&#10; &#125;&#10; vpc-sc &#61; &#123;&#10; filter &#61; &#34;protoPayload.metadata.&#64;type&#61;&#92;&#34;type.googleapis.com&#47;google.cloud.audit.VpcServiceControlAuditMetadata&#92;&#34;&#34;&#10; type &#61; &#34;bigquery&#34;&#10; &#125;&#10;&#125;">&#123;&#8230;&#125;</code> |
| [vpc_sc_access_levels](variables.tf#L161) | VPC SC access level definitions. | <code title="map&#40;object&#40;&#123;&#10; combining_function &#61; optional&#40;string&#41;&#10; conditions &#61; optional&#40;list&#40;object&#40;&#123;&#10; device_policy &#61; optional&#40;object&#40;&#123;&#10; allowed_device_management_levels &#61; optional&#40;list&#40;string&#41;&#41;&#10; allowed_encryption_statuses &#61; optional&#40;list&#40;string&#41;&#41;&#10; require_admin_approval &#61; bool&#10; require_corp_owned &#61; bool&#10; require_screen_lock &#61; optional&#40;bool&#41;&#10; os_constraints &#61; optional&#40;list&#40;object&#40;&#123;&#10; os_type &#61; string&#10; minimum_version &#61; optional&#40;string&#41;&#10; require_verified_chrome_os &#61; optional&#40;bool&#41;&#10; &#125;&#41;&#41;&#41;&#10; &#125;&#41;&#41;&#10; ip_subnetworks &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; members &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; negate &#61; optional&#40;bool&#41;&#10; regions &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; required_access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; description &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [vpc_sc_access_levels](variables.tf#L162) | VPC SC access level definitions. | <code title="map&#40;object&#40;&#123;&#10; combining_function &#61; optional&#40;string&#41;&#10; conditions &#61; optional&#40;list&#40;object&#40;&#123;&#10; device_policy &#61; optional&#40;object&#40;&#123;&#10; allowed_device_management_levels &#61; optional&#40;list&#40;string&#41;&#41;&#10; allowed_encryption_statuses &#61; optional&#40;list&#40;string&#41;&#41;&#10; require_admin_approval &#61; bool&#10; require_corp_owned &#61; bool&#10; require_screen_lock &#61; optional&#40;bool&#41;&#10; os_constraints &#61; optional&#40;list&#40;object&#40;&#123;&#10; os_type &#61; string&#10; minimum_version &#61; optional&#40;string&#41;&#10; require_verified_chrome_os &#61; optional&#40;bool&#41;&#10; &#125;&#41;&#41;&#41;&#10; &#125;&#41;&#41;&#10; ip_subnetworks &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; members &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; negate &#61; optional&#40;bool&#41;&#10; regions &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; required_access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; description &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [vpc_sc_egress_policies](variables.tf#L190) | VPC SC egress policy definitions. | <code title="map&#40;object&#40;&#123;&#10; from &#61; object&#40;&#123;&#10; identity_type &#61; optional&#40;string, &#34;ANY_IDENTITY&#34;&#41;&#10; identities &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#10; to &#61; object&#40;&#123;&#10; operations &#61; optional&#40;list&#40;object&#40;&#123;&#10; method_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; resource_type_external &#61; optional&#40;bool, false&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [vpc_sc_egress_policies](variables.tf#L191) | VPC SC egress policy definitions. | <code title="map&#40;object&#40;&#123;&#10; from &#61; object&#40;&#123;&#10; identity_type &#61; optional&#40;string, &#34;ANY_IDENTITY&#34;&#41;&#10; identities &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#10; to &#61; object&#40;&#123;&#10; operations &#61; optional&#40;list&#40;object&#40;&#123;&#10; method_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; resource_type_external &#61; optional&#40;bool, false&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [vpc_sc_ingress_policies](variables.tf#L210) | VPC SC ingress policy definitions. | <code title="map&#40;object&#40;&#123;&#10; from &#61; object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; identity_type &#61; optional&#40;string&#41;&#10; identities &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#10; to &#61; object&#40;&#123;&#10; operations &#61; optional&#40;list&#40;object&#40;&#123;&#10; method_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [vpc_sc_ingress_policies](variables.tf#L211) | VPC SC ingress policy definitions. | <code title="map&#40;object&#40;&#123;&#10; from &#61; object&#40;&#123;&#10; access_levels &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; identity_type &#61; optional&#40;string&#41;&#10; identities &#61; optional&#40;list&#40;string&#41;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;&#10; to &#61; object&#40;&#123;&#10; operations &#61; optional&#40;list&#40;object&#40;&#123;&#10; method_selectors &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_name &#61; string&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; resources &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs ## Outputs
@ -180,7 +179,6 @@ terraform apply
| [folders](outputs.tf#L15) | Folders id. | | | [folders](outputs.tf#L15) | Folders id. | |
| [folders_sink_writer_identities](outputs.tf#L23) | Folders id. | | | [folders_sink_writer_identities](outputs.tf#L23) | Folders id. | |
| [kms_keys](outputs.tf#L31) | Cloud KMS encryption keys created. | | | [kms_keys](outputs.tf#L31) | Cloud KMS encryption keys created. | |
<!-- END TFDOC --> <!-- END TFDOC -->
## Test ## Test
@ -209,5 +207,5 @@ module "test" {
billing_account_id = "123456-123456-123456" billing_account_id = "123456-123456-123456"
} }
} }
# tftest modules=6 resources=38 inventory=simple.yaml # tftest modules=7 resources=38
``` ```

View File

@ -0,0 +1,37 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
priority: 1000
match:
source_ranges:
- rfc1918
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
priority: 1001
match:
source_ranges:
- healthchecks
layer4_configs:
- protocol: tcp
ports: ["80", "443"]
allow-ssh-from-iap:
description: Enable SSH from IAP
priority: 1002
match:
source_ranges:
- 35.235.240.0/20
layer4_configs:
- protocol: tcp
ports: ["22"]
allow-icmp:
description: Enable ICMP
priority: 1003
match:
source_ranges:
- 0.0.0.0/0
layer4_configs:
- protocol: icmp

View File

@ -1,50 +0,0 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
direction: INGRESS
action: allow
priority: 1000
ranges:
- $rfc1918
ports:
all: []
target_resources: null
enable_logging: false
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
direction: INGRESS
action: allow
priority: 1001
ranges:
- $healthchecks
ports:
tcp: ["80", "443"]
target_resources: null
enable_logging: false
allow-ssh-from-iap:
description: Enable SSH from IAP
direction: INGRESS
action: allow
priority: 1002
ranges:
- 35.235.240.0/20
ports:
tcp: ["22"]
target_resources: null
enable_logging: false
allow-icmp:
description: Enable ICMP
direction: INGRESS
action: allow
priority: 1003
ranges:
- 0.0.0.0/0
ports:
icmp: []
target_resources: null
enable_logging: false

View File

@ -25,12 +25,9 @@ locals {
for k, v in var.kms_keys : k => v if contains(v.locations, loc) for k, v in var.kms_keys : k => v if contains(v.locations, loc)
} }
} }
kms_log_locations = distinct(flatten([ kms_log_locations = distinct(flatten([
for k, v in local.kms_log_sink_keys : compact(v.locations) for k, v in local.kms_log_sink_keys : compact(v.locations)
])) ]))
# Log sink keys
kms_log_sink_keys = { kms_log_sink_keys = {
"storage" = { "storage" = {
labels = {} labels = {}
@ -61,8 +58,12 @@ module "sec-project" {
name = var.project_config.project_ids["sec-core"] name = var.project_config.project_ids["sec-core"]
parent = module.folder.id parent = module.folder.id
billing_account = var.project_config.billing_account_id billing_account = var.project_config.billing_account_id
project_create = var.project_config.billing_account_id != null && var.enable_features.encryption project_create = (
prefix = var.project_config.billing_account_id == null ? null : var.prefix var.project_config.billing_account_id != null && var.enable_features.encryption
)
prefix = (
var.project_config.billing_account_id == null ? null : var.prefix
)
group_iam = { group_iam = {
(local.groups.workload-security) = [ (local.groups.workload-security) = [
"roles/editor" "roles/editor"
@ -76,17 +77,23 @@ module "sec-project" {
} }
module "sec-kms" { module "sec-kms" {
for_each = var.enable_features.encryption ? toset(local.kms_locations) : toset([]) for_each = (
var.enable_features.encryption
? toset(local.kms_locations)
: toset([])
)
source = "../../../modules/kms" source = "../../../modules/kms"
project_id = module.sec-project[0].project_id project_id = module.sec-project[0].project_id
keyring = { keyring = {
location = each.key location = each.key
name = "sec-${each.key}" name = "sec-${each.key}"
} }
# rename to `key_iam` to switch to authoritative bindings key_iam = {
key_iam_additive = {
for k, v in local.kms_locations_keys[each.key] : k => v.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] keys = local.kms_locations_keys[each.key]
} }

View File

@ -78,11 +78,6 @@ module "folder" {
id = var.folder_config.folder_create != null ? null : var.folder_config.folder_id id = var.folder_config.folder_create != null ? null : var.folder_config.folder_id
group_iam = local.group_iam group_iam = local.group_iam
org_policies_data_path = var.data_dir != null ? "${var.data_dir}/org-policies" : null org_policies_data_path = var.data_dir != null ? "${var.data_dir}/org-policies" : null
firewall_policy_factory = var.data_dir != null ? {
cidr_file = "${var.data_dir}/firewall-policies/cidrs.yaml"
policy_name = "${var.prefix}-fw-policy"
rules_file = "${var.data_dir}/firewall-policies/hierarchical-policy-rules.yaml"
} : null
logging_sinks = var.enable_features.log_sink ? { logging_sinks = var.enable_features.log_sink ? {
for name, attrs in var.log_sinks : name => { for name, attrs in var.log_sinks : name => {
bq_partitioned_table = attrs.type == "bigquery" bq_partitioned_table = attrs.type == "bigquery"
@ -93,14 +88,24 @@ module "folder" {
} : null } : null
} }
module "firewall-policy" {
source = "../../../modules/net-firewall-policy"
name = "default"
parent_id = module.folder.id
rules_factory_config = var.data_dir == null ? {} : {
cidr_file_path = "${var.data_dir}/firewall-policies/cidrs.yaml"
ingress_rules_file_path = "${var.data_dir}/firewall-policies/hierarchical-ingress-rules.yaml"
}
}
module "folder-workload" { module "folder-workload" {
source = "../../../modules/folder" source = "../../../modules/folder"
parent = module.folder.id parent = module.folder.id
name = "${var.prefix}-workload" name = "${var.prefix}-workload"
} }
#TODO VPCSC: Access levels
#TODO VPCSC: Access levels
data "google_projects" "folder-projects" { data "google_projects" "folder-projects" {
filter = "parent.id:${split("/", module.folder.id)[1]}" filter = "parent.id:${split("/", module.folder.id)[1]}"

View File

@ -75,10 +75,11 @@ variable "groups" {
variable "kms_keys" { variable "kms_keys" {
description = "KMS keys to create, keyed by name." description = "KMS keys to create, keyed by name."
type = map(object({ type = map(object({
iam = optional(map(list(string)), {}) iam = optional(map(list(string)), {})
labels = optional(map(string), {}) iam_bindings_additive = optional(map(map(any)), {})
locations = optional(list(string), ["global", "europe", "europe-west1"]) labels = optional(map(string), {})
rotation_period = optional(string, "7776000s") locations = optional(list(string), ["global", "europe", "europe-west1"])
rotation_period = optional(string, "7776000s")
})) }))
default = {} default = {}
} }

View File

@ -73,9 +73,6 @@ module "project" {
"compute.googleapis.com", "compute.googleapis.com",
"secretmanager.googleapis.com", "secretmanager.googleapis.com",
] ]
iam = {}
iam_additive = {}
shared_vpc_service_config = var.shared_vpc_project_id == null ? null : { shared_vpc_service_config = var.shared_vpc_project_id == null ? null : {
attach = true attach = true
host_project = var.shared_vpc_project_id host_project = var.shared_vpc_project_id

View File

@ -1,270 +1,95 @@
# Minimal Project Factory # Project Factory
This module implements a minimal, opinionated project factory (see [Factories](../README.md) for rationale) that allows for the creation of projects. This is a working example of how to manage project creation at scale, by wrapping the [project module](../../../modules/project/) and driving it via external data, either directly provided or parsed via YAML files.
While the module can be invoked by manually populating the required variables, its interface is meant for the massive creation of resources leveraging a set of well-defined YaML documents, as shown in the examples below. The wrapping layer around the project module is intentionally thin, so that
The Project Factory is meant to be executed by a Service Account (or a regular user) having this minimal set of permissions over your resources: - all the features of the project module are available
- no "magic" or hidden side effects are implemented in code
- debugging and integration of new features is simple
* **Org level** - a custom role for networking operations including the following permissions The code is meant to be executed by a high level service accounts with powerful permissions:
* `"compute.organizations.enableXpnResource"`,
* `"compute.organizations.disableXpnResource"`, - Shared VPC connection if service project attachment is desired
* `"compute.subnetworks.setIamPolicy"`, - project creation on the nodes (folder or org) where projects will be defined
* `"dns.networks.bindPrivateDNSZone"`
* and role `"roles/orgpolicy.policyAdmin"` The module also supports optional creation of specific resources that usually part of the project creation flow:
* **on each folder** where projects will be created
* `"roles/logging.admin"` - service accounts used for VM instances, and associated basic roles
* `"roles/owner"` - KMS key encrypt/decrypt permissions for service identities in the project
* `"roles/resourcemanager.folderAdmin"` - membership in VPC SC standard or bridge perimeters
* `"roles/resourcemanager.projectCreator"`
* **on the host project** for the Shared VPC/s Compared to the previous version of this code, network-related resources (DNS zones, VPC subnets, etc.) have been removed as they are not typically in scope for the team who manages project creation, and adding them when needed requires just a few trivial code changes.
* `"roles/browser"`
* `"roles/compute.viewer"`
* `"roles/dns.admin"`
## Example ## Example
### Directory structure
```
.
├── data
│ ├── defaults.yaml
│ └── projects
│ ├── project-example-one.yaml
│ ├── project-example-two.yaml
│ └── project-example-three.yaml
├── main.tf
└── terraform.tfvars
```
### Terraform code
```hcl ```hcl
locals { module "project-factory" {
defaults = yamldecode(file(local._defaults_file)) source = "./fabric/blueprints/factories/project-factory"
projects = { data_defaults = {
for f in fileset("${local._data_dir}", "**/*.yaml") : billing_account = "012345-67890A-ABCDEF"
trimsuffix(f, ".yaml") => yamldecode(file("${local._data_dir}/${f}")) }
data_merges = {
labels = {
environment = "test"
}
services = [
"stackdriver.googleapis.com"
]
}
data_overrides = {
contacts = {
"admin@example.com" = ["ALL"]
}
prefix = "test-pf"
}
factory_data = {
data_path = "data"
} }
# these are usually set via variables
_base_dir = "./fabric/blueprints/factories/project-factory"
_data_dir = "${local._base_dir}/sample-data/projects/"
_defaults_file = "${local._base_dir}/sample-data/defaults.yaml"
} }
# tftest modules=6 resources=12 files=prj-app-1,prj-app-2 inventory=example.yaml
module "projects" {
source = "./fabric/blueprints/factories/project-factory"
for_each = local.projects
defaults = local.defaults
project_id = each.key
descriptive_name = try(each.value.descriptive_name, null)
billing_account_id = try(each.value.billing_account_id, null)
billing_alert = try(each.value.billing_alert, null)
dns_zones = try(each.value.dns_zones, [])
essential_contacts = try(each.value.essential_contacts, [])
folder_id = each.value.folder_id
group_iam = try(each.value.group_iam, {})
iam = try(each.value.iam, {})
kms_service_agents = try(each.value.kms_service_agents, {})
labels = try(each.value.labels, {})
org_policies = try(each.value.org_policies, {})
prefix = each.value.prefix
service_accounts = try(each.value.service_accounts, {})
services = try(each.value.services, [])
service_identities_iam = try(each.value.service_identities_iam, {})
vpc = try(each.value.vpc, null)
}
# tftest modules=7 resources=38 inventory=example.yaml
```
### Projects configuration
```yaml
# ./data/defaults.yaml
# The following applies as overridable defaults for all projects
# All attributes are required
billing_account_id: 012345-67890A-BCDEF0
billing_alert:
amount: 1000
thresholds:
current: [0.5, 0.8]
forecasted: [0.5, 0.8]
credit_treatment: INCLUDE_ALL_CREDITS
environment_dns_zone: prod.gcp.example.com
essential_contacts: []
labels:
environment: production
department: legal
application: my-legal-bot
notification_channels: []
shared_vpc_self_link: https://www.googleapis.com/compute/v1/projects/project-example-host-project/global/networks/vpc-one
vpc_host_project: project-example-host-project
``` ```
```yaml ```yaml
# ./data/projects/project-example-one.yaml billing_account: 012345-67890A-BCDEF0
# One file per project - projects will be named after the filename
# [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: []
# [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: labels:
environment: prod app: app-1
team: foo
# [opt] Org policy overrides defined at project level service_encryption_key_ids:
org_policies: compute:
compute.disableGuestAttributesAccess: - projects/kms-central-prj/locations/europe-west3/keyRings/my-keyring/cryptoKeys/europe3-gce
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] IAM bindings on the service account resources.
# in name => {role => [members]} format
service_accounts_iam:
another-service-account:
- roles/iam.serviceAccountTokenCreator:
- group: app-team-1@example.com
# [opt] APIs to enable on the project.
services: services:
- storage.googleapis.com - storage.googleapis.com
- stackdriver.googleapis.com service_accounts:
- compute.googleapis.com app-1-be: {}
app-1-fe: {}
# [opt] Roles to assign to the robots service accounts in robot => [roles] format # tftest-file id=prj-app-1 path=data/prj-app-1.yaml
services_iam: ```
compute:
- roles/storage.objectViewer
# [opt] VPC setup. ```yaml
# If set enables the `compute.googleapis.com` service and configures labels:
# service project attachment app: app-1
vpc: team: foo
service_accounts:
app-2-be: {}
# [opt] If set, enables the container API # tftest-file id=prj-app-2 path=data/prj-app-2.yaml
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-prod-net-spoke-0
# [opt] Services for which set up the IAM in the host project
service_iam_grants:
- dataproc.googleapis.com
# [opt] Roles to rant service project service identities in host project
service_identity_iam:
"roles/compute.networkUser":
- cloudservices
- container-engine
# [opt] Subnets in the host project where principals will be granted networkUser
# in region/subnet-name => [principals]
subnets_iam:
europe-west1/prod-default-ew1:
- user:foobar@example.com
- serviceAccount:service-account1@my-project.iam.gserviceaccount.com
``` ```
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Variables ## Variables
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [billing_account_id](variables.tf#L17) | Billing account id. | <code>string</code> | ✓ | | | [factory_data](variables.tf#L83) | Project data from either YAML files or externally parsed data. | <code title="object&#40;&#123;&#10; data &#61; optional&#40;map&#40;any&#41;&#41;&#10; data_path &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [prefix](variables.tf#L144) | Prefix used for resource names. | <code>string</code> | ✓ | | | [data_defaults](variables.tf#L17) | Optional default values used when corresponding project data from files are missing. | <code title="object&#40;&#123;&#10; billing_account &#61; optional&#40;string&#41;&#10; contacts &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; labels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; metric_scopes &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; prefix &#61; optional&#40;string&#41;&#10; service_encryption_key_ids &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_perimeter_bridges &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; service_perimeter_standard &#61; optional&#40;string&#41;&#10; services &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; shared_vpc_service_config &#61; optional&#40;object&#40;&#123;&#10; host_project &#61; string&#10; service_identity_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_iam_grants &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;, &#123; host_project &#61; null &#125;&#41;&#10; tag_bindings &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; service_accounts &#61; optional&#40;map&#40;object&#40;&#123;&#10; default_roles &#61; optional&#40;bool, true&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [project_id](variables.tf#L153) | Project id. | <code>string</code> | ✓ | | | [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`. | <code title="object&#40;&#123;&#10; contacts &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; labels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; metric_scopes &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; service_encryption_key_ids &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_perimeter_bridges &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; services &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; tag_bindings &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; service_accounts &#61; optional&#40;map&#40;object&#40;&#123;&#10; default_roles &#61; optional&#40;bool, true&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [billing_alert](variables.tf#L22) | Billing alert configuration. | <code title="object&#40;&#123;&#10; amount &#61; number&#10; thresholds &#61; object&#40;&#123;&#10; current &#61; list&#40;number&#41;&#10; forecasted &#61; list&#40;number&#41;&#10; &#125;&#41;&#10; credit_treatment &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [data_overrides](variables.tf#L63) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | <code title="object&#40;&#123;&#10; billing_account &#61; optional&#40;string&#41;&#10; contacts &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10; prefix &#61; optional&#40;string&#41;&#10; service_encryption_key_ids &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10; service_perimeter_bridges &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_perimeter_standard &#61; optional&#40;string&#41;&#10; tag_bindings &#61; optional&#40;map&#40;string&#41;&#41;&#10; services &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_accounts &#61; optional&#40;map&#40;object&#40;&#123;&#10; default_roles &#61; optional&#40;bool, true&#41;&#10; &#125;&#41;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [defaults](variables.tf#L35) | Project factory default values. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; billing_alert &#61; object&#40;&#123;&#10; amount &#61; number&#10; thresholds &#61; object&#40;&#123;&#10; current &#61; list&#40;number&#41;&#10; forecasted &#61; list&#40;number&#41;&#10; &#125;&#41;&#10; credit_treatment &#61; string&#10; &#125;&#41;&#10; environment_dns_zone &#61; string&#10; essential_contacts &#61; list&#40;string&#41;&#10; labels &#61; map&#40;string&#41;&#10; notification_channels &#61; list&#40;string&#41;&#10; shared_vpc_self_link &#61; string&#10; vpc_host_project &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [descriptive_name](variables.tf#L57) | Name of the project name. Used for project name instead of `name` variable. | <code>string</code> | | <code>null</code> |
| [dns_zones](variables.tf#L63) | DNS private zones to create as child of var.defaults.environment_dns_zone. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [essential_contacts](variables.tf#L69) | Email contacts to be used for billing and GCP notifications. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [folder_id](variables.tf#L75) | Folder ID for the folder where the project will be created. | <code>string</code> | | <code>null</code> |
| [group_iam](variables.tf#L81) | Custom IAM settings in group => [role] format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [group_iam_additive](variables.tf#L87) | Custom additive IAM settings in group => [role] format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam](variables.tf#L93) | Custom IAM settings in role => [principal] format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive](variables.tf#L99) | Custom additive IAM settings in role => [principal] format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [kms_service_agents](variables.tf#L105) | KMS IAM configuration in as service => [key]. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [labels](variables.tf#L111) | Labels to be assigned at project level. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies](variables.tf#L117) | Org-policy overrides at project level. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool&#41; &#35; for boolean policies only.&#10; condition &#61; optional&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_accounts](variables.tf#L158) | Service accounts to be created, and roles assigned them on the project. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_accounts_additive](variables.tf#L164) | Service accounts to be created, and roles assigned them on the project additively. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_accounts_iam](variables.tf#L170) | IAM bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_accounts_iam_additive](variables.tf#L177) | IAM additive bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_identities_iam](variables.tf#L184) | Custom IAM settings for service identities in service => [role] format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_identities_iam_additive](variables.tf#L191) | Custom additive IAM settings for service identities in service => [role] format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [services](variables.tf#L198) | Services to be enabled for the project. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [vpc](variables.tf#L205) | VPC configuration for the project. | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; gke_setup &#61; optional&#40;object&#40;&#123;&#10; enable_security_admin &#61; optional&#40;bool, false&#41;&#10; enable_host_service_agent &#61; optional&#40;bool, false&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; service_iam_grants &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; service_identity_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; subnets_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; host_project &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> |
## Outputs ## Outputs
| name | description | sensitive | | name | description | sensitive |
|---|---|:---:| |---|---|:---:|
| [project](outputs.tf#L19) | The project resource as return by the `project` module. | | | [projects](outputs.tf#L17) | Project module outputs. | |
| [project_id](outputs.tf#L29) | Project ID. | | | [service_accounts](outputs.tf#L22) | Service account emails. | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -0,0 +1,100 @@
/**
* 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 {
_data = (
var.factory_data.data != null
? var.factory_data.data
: {
for f in fileset("${local._data_path}", "**/*.yaml") :
trimsuffix(f, ".yaml") => yamldecode(file("${local._data_path}/${f}"))
}
)
_data_path = var.factory_data.data_path == null ? null : pathexpand(
var.factory_data.data_path
)
projects = {
for k, v in local._data : k => merge(v, {
billing_account = coalesce(
var.data_overrides.billing_account,
try(v.billing_account, null),
var.data_defaults.billing_account
)
contacts = coalesce(
var.data_overrides.contacts,
try(v.contacts, null),
var.data_defaults.contacts
)
labels = coalesce(
try(v.labels, null),
var.data_defaults.labels
)
metric_scopes = coalesce(
try(v.metric_scopes, null),
var.data_defaults.metric_scopes
)
prefix = coalesce(
var.data_overrides.prefix,
try(v.prefix, null),
var.data_defaults.prefix
)
service_encryption_key_ids = coalesce(
var.data_overrides.service_encryption_key_ids,
try(v.service_encryption_key_ids, null),
var.data_defaults.service_encryption_key_ids
)
service_perimeter_bridges = coalesce(
var.data_overrides.service_perimeter_bridges,
try(v.service_perimeter_bridges, null),
var.data_defaults.service_perimeter_bridges
)
service_perimeter_standard = try(coalesce(
var.data_overrides.service_perimeter_standard,
try(v.service_perimeter_standard, null),
var.data_defaults.service_perimeter_standard
), null)
services = coalesce(
var.data_overrides.services,
try(v.services, null),
var.data_defaults.services
)
shared_vpc_service_config = coalesce(
try(v.shared_vpc_service_config, null),
var.data_defaults.shared_vpc_service_config
)
tag_bindings = coalesce(
var.data_overrides.tag_bindings,
try(v.tag_bindings, null),
var.data_defaults.tag_bindings
)
# non-project resources
service_accounts = coalesce(
var.data_overrides.service_accounts,
try(v.service_accounts, null),
var.data_defaults.service_accounts
)
})
}
service_accounts = flatten([
for k, v in local.projects : [
for name, opts in v.service_accounts : {
project = k
name = name
options = opts
}
]
])
}

View File

@ -14,222 +14,68 @@
* limitations under the License. * limitations under the License.
*/ */
locals { module "projects" {
_gke_config_service_identity_iam = { source = "../../../modules/project"
"roles/compute.networkUser" = compact([ for_each = local.projects
var.vpc.gke_setup.enable_host_service_agent ? "container-engine" : null, billing_account = each.value.billing_account
local.vpc_cloudservices ? "cloudservices" : null name = each.key
]) parent = try(each.value.parent, null)
"roles/compute.securityAdmin" = compact([ prefix = each.value.prefix
var.vpc.gke_setup.enable_security_admin ? "container-engine" : null, auto_create_network = try(each.value.auto_create_network, false)
]) compute_metadata = try(each.value.compute_metadata, {})
"roles/container.hostServiceAgentUser" = compact([ # TODO: concat lists for each key
var.vpc.gke_setup.enable_host_service_agent ? "container-engine" : null contacts = merge(
]) each.value.contacts, var.data_merges.contacts
} )
default_service_account = try(each.value.default_service_account, "keep")
_group_iam = { descriptive_name = try(each.value.descriptive_name, null)
for r in local._group_iam_bindings : r => [ group_iam = try(each.value.group_iam, {})
for k, v in var.group_iam : iam = try(each.value.iam, {})
"group:${k}" if try(index(v, r), null) != null iam_bindings = try(each.value.iam_bindings, {})
] iam_bindings_additive = try(each.value.iam_bindings_additive, {})
} labels = each.value.labels
_group_iam_additive = { lien_reason = try(each.value.lien_reason, null)
for r in local._group_iam_additive_bindings : r => [ logging_data_access = try(each.value.logging_data_access, {})
for k, v in var.group_iam_additive : logging_exclusions = try(each.value.logging_exclusions, {})
"group:${k}" if try(index(v, r), null) != null logging_sinks = try(each.value.logging_sinks, {})
] metric_scopes = distinct(concat(
} each.value.metric_scopes, var.data_merges.metric_scopes
_group_iam_bindings = distinct(flatten(values(var.group_iam)))
_group_iam_additive_bindings = distinct(flatten(values(var.group_iam_additive)))
_service_accounts_iam = {
for r in local._service_accounts_iam_bindings : r => [
for k, v in var.service_accounts :
module.service-accounts[k].iam_email
if try(index(v, r), null) != null
]
}
_service_accounts_iam_bindings = distinct(flatten(
values(var.service_accounts)
)) ))
_service_accounts_iam_additive = { service_encryption_key_ids = merge(
for r in local._service_accounts_iam_additive_bindings : r => [ each.value.service_encryption_key_ids,
for k, v in var.service_accounts_additive : var.data_merges.service_encryption_key_ids
module.service-accounts[k].iam_email )
if try(index(v, r), null) != null service_perimeter_bridges = distinct(concat(
] each.value.service_perimeter_bridges,
} var.data_merges.service_perimeter_bridges
_service_accounts_iam_additive_bindings = distinct(flatten(
values(var.service_accounts_additive)
)) ))
_services = concat([ service_perimeter_standard = each.value.service_perimeter_standard
"billingbudgets.googleapis.com", services = distinct(concat(
"essentialcontacts.googleapis.com", each.value.services,
"orgpolicy.googleapis.com", var.data_merges.services
], ))
length(var.dns_zones) > 0 ? ["dns.googleapis.com"] : [], shared_vpc_service_config = each.value.shared_vpc_service_config
try(var.vpc.gke_setup, null) != null ? ["container.googleapis.com"] : [], tag_bindings = merge(
var.vpc != null ? ["compute.googleapis.com"] : [], each.value.tag_bindings,
var.data_merges.tag_bindings
) )
_service_identities_roles = distinct(flatten(values(var.service_identities_iam)))
_service_identities_iam = {
for role in local._service_identities_roles : role => [
for service, roles in var.service_identities_iam :
"serviceAccount:${module.project.service_accounts.robots[service]}"
if contains(roles, role)
]
}
_service_identities_roles_additive = distinct(flatten(values(var.service_identities_iam_additive)))
_service_identities_iam_additive = {
for role in local._service_identities_roles_additive : role => [
for service, roles in var.service_identities_iam_additive :
"serviceAccount:${module.project.service_accounts.robots[service]}"
if contains(roles, role)
]
}
_vpc_subnet_bindings = (
var.vpc.subnets_iam == null || var.vpc.host_project == null
? []
: flatten([
for subnet, members in var.vpc.subnets_iam : [
for member in members : {
region = split("/", subnet)[0]
subnet = split("/", subnet)[1]
member = member
}
]
])
)
billing_account_id = coalesce(
var.billing_account_id, try(var.defaults.billing_account_id, "")
)
billing_alert = (
var.billing_alert == null
? try(var.defaults.billing_alert, null)
: var.billing_alert
)
essential_contacts = concat(
try(var.defaults.essential_contacts, []), var.essential_contacts
)
iam = {
for role in distinct(concat(
keys(var.iam),
keys(local._group_iam),
keys(local._service_accounts_iam),
keys(local._service_identities_iam),
)) :
role => concat(
try(var.iam[role], []),
try(local._group_iam[role], []),
try(local._service_accounts_iam[role], []),
try(local._service_identities_iam[role], []),
)
}
iam_additive = {
for role in distinct(concat(
keys(var.iam_additive),
keys(local._group_iam_additive),
keys(local._service_accounts_iam_additive),
keys(local._service_identities_iam_additive),
)) :
role => concat(
try(var.iam_additive[role], []),
try(local._group_iam_additive[role], []),
try(local._service_accounts_iam_additive[role], []),
try(local._service_identities_iam_additive[role], []),
)
}
labels = merge(
coalesce(var.labels, {}), coalesce(try(var.defaults.labels, {}), {})
)
services = distinct(concat(var.services, local._services))
vpc_cloudservices = (
var.vpc.gke_setup.enable_host_service_agent ||
contains(var.services, "compute.googleapis.com")
)
vpc_service_identity_iam = {
for role in setunion(keys(local._gke_config_service_identity_iam), keys(var.vpc.service_identity_iam)) :
role => setunion(
lookup(local._gke_config_service_identity_iam, role, []),
lookup(var.vpc.service_identity_iam, role, []),
)
}
vpc_subnet_bindings = {
for binding in local._vpc_subnet_bindings :
"${binding.subnet}:${binding.member}" => binding
}
}
module "billing-alert" {
for_each = local.billing_alert == null ? {} : { 1 = 1 }
source = "../../../modules/billing-budget"
billing_account = local.billing_account_id
name = "${module.project.project_id} budget"
amount = local.billing_alert.amount
thresholds = local.billing_alert.thresholds
credit_treatment = local.billing_alert.credit_treatment
notification_channels = var.defaults.notification_channels
projects = ["projects/${module.project.number}"]
email_recipients = {
project_id = module.project.project_id
emails = local.essential_contacts
}
}
module "dns" {
source = "../../../modules/dns"
for_each = toset(var.dns_zones)
project_id = coalesce(var.vpc.host_project, module.project.project_id)
name = each.value
zone_config = {
domain = "${each.value}.${var.defaults.environment_dns_zone}"
private = {
client_networks = [var.defaults.shared_vpc_self_link]
}
}
}
module "project" {
source = "../../../modules/project"
billing_account = local.billing_account_id
name = var.project_id
descriptive_name = var.descriptive_name
prefix = var.prefix
contacts = { for c in local.essential_contacts : c => ["ALL"] }
iam = local.iam
iam_additive = local.iam_additive
labels = local.labels
org_policies = try(var.org_policies, {})
parent = var.folder_id
service_encryption_key_ids = var.kms_service_agents
services = local.services
shared_vpc_service_config = var.vpc == null ? null : {
host_project = var.vpc.host_project
# these are non-authoritative
service_identity_iam = local.vpc_service_identity_iam
service_iam_grants = var.vpc.service_iam_grants
}
} }
module "service-accounts" { module "service-accounts" {
source = "../../../modules/iam-service-account" source = "../../../modules/iam-service-account"
for_each = var.service_accounts for_each = {
name = each.key for k in local.service_accounts : "${k.project}-${k.name}" => k
project_id = module.project.project_id }
iam = lookup(var.service_accounts_iam, each.key, null) name = each.value.name
} project_id = module.projects[each.value.project].project_id
iam_project_roles = (
resource "google_compute_subnetwork_iam_member" "default" { try(each.value.options.default_roles, null) == null
for_each = local.vpc_subnet_bindings ? {}
project = var.vpc.host_project : {
subnetwork = "projects/${var.vpc.host_project}/regions/${each.value.region}/subnetworks/${each.value.subnet}" (module.projects[each.value.project].project_id) = [
region = each.value.region "roles/logging.logWriter",
role = "roles/compute.networkUser" "roles/monitoring.metricWriter"
member = ( ]
lookup(var.service_accounts, each.value.member, null) != null }
? module.service-accounts[each.value.member].iam_email
: each.value.member
) )
} }

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2022 Google LLC * Copyright 2023 Google LLC
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,23 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
# TODO(): proper outputs output "projects" {
description = "Project module outputs."
output "project" { value = module.projects
description = "The project resource as return by the `project` module."
value = module.project
depends_on = [
google_compute_subnetwork_iam_member.default,
module.dns
]
} }
output "project_id" { output "service_accounts" {
description = "Project ID." description = "Service account emails."
value = module.project.project_id # TODO: group by project
depends_on = [ value = {
google_compute_subnetwork_iam_member.default, for k, v in module.service-accounts : k => v.email
module.dns }
]
} }

View File

@ -1,29 +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
environment_dns_zone: dev.example.org
# [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: []
shared_vpc_self_link: projects/foo/networks/bar
prefix: test
vpc_host_project:

View File

@ -1,117 +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: dev2
costcenter: apps
# [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] Prefix - overrides default if set
prefix: test1
# [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.adminv1
# [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] Services for which set up the IAM in the host project
service_iam_grants:
- dataproc.googleapis.com
# [opt] Roles to rant service project service identities in host project
service_identity_iam:
"roles/compute.networkUser":
- cloudservices
- container-engine
# [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:my-service-account

View File

@ -14,215 +14,84 @@
* limitations under the License. * limitations under the License.
*/ */
variable "billing_account_id" { variable "data_defaults" {
description = "Billing account id." description = "Optional default values used when corresponding project data from files are missing."
type = string
}
variable "billing_alert" {
description = "Billing alert configuration."
type = object({ type = object({
amount = number billing_account = optional(string)
thresholds = object({ contacts = optional(map(list(string)), {})
current = list(number) labels = optional(map(string), {})
forecasted = list(number) metric_scopes = optional(list(string), [])
}) prefix = optional(string)
credit_treatment = string service_encryption_key_ids = optional(map(list(string)), {})
service_perimeter_bridges = optional(list(string), [])
service_perimeter_standard = optional(string)
services = optional(list(string), [])
shared_vpc_service_config = optional(object({
host_project = string
service_identity_iam = optional(map(list(string)), {})
service_iam_grants = optional(list(string), [])
}), { host_project = null })
tag_bindings = optional(map(string), {})
# non-project resources
service_accounts = optional(map(object({
default_roles = optional(bool, true)
})), {})
}) })
default = null nullable = false
}
variable "defaults" {
description = "Project factory default values."
type = object({
billing_account_id = string
billing_alert = object({
amount = number
thresholds = object({
current = list(number)
forecasted = list(number)
})
credit_treatment = string
})
environment_dns_zone = string
essential_contacts = list(string)
labels = map(string)
notification_channels = list(string)
shared_vpc_self_link = string
vpc_host_project = string
})
default = null
}
variable "descriptive_name" {
description = "Name of the project name. Used for project name instead of `name` variable."
type = string
default = null
}
variable "dns_zones" {
description = "DNS private zones to create as child of var.defaults.environment_dns_zone."
type = list(string)
default = []
}
variable "essential_contacts" {
description = "Email contacts to be used for billing and GCP notifications."
type = list(string)
default = []
}
variable "folder_id" {
description = "Folder ID for the folder where the project will be created."
type = string
default = null
}
variable "group_iam" {
description = "Custom IAM settings in group => [role] format."
type = map(list(string))
default = {}
}
variable "group_iam_additive" {
description = "Custom additive IAM settings in group => [role] format."
type = map(list(string))
default = {}
}
variable "iam" {
description = "Custom IAM settings in role => [principal] format."
type = map(list(string))
default = {}
}
variable "iam_additive" {
description = "Custom additive IAM settings in role => [principal] format."
type = map(list(string))
default = {}
}
variable "kms_service_agents" {
description = "KMS IAM configuration in as service => [key]."
type = map(list(string))
default = {}
}
variable "labels" {
description = "Labels to be assigned at project level."
type = map(string)
default = {}
}
variable "org_policies" {
description = "Org-policy overrides at project level."
type = map(object({
inherit_from_parent = optional(bool) # for list policies only.
reset = optional(bool)
rules = optional(list(object({
allow = optional(object({
all = optional(bool)
values = optional(list(string))
}))
deny = optional(object({
all = optional(bool)
values = optional(list(string))
}))
enforce = optional(bool) # for boolean policies only.
condition = optional(object({
description = optional(string)
expression = optional(string)
location = optional(string)
title = optional(string)
}), {})
})), [])
}))
default = {} default = {}
nullable = false
} }
variable "prefix" { variable "data_merges" {
description = "Prefix used for resource names." description = "Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`."
type = string
validation {
condition = var.prefix != ""
error_message = "Prefix cannot be empty."
}
}
variable "project_id" {
description = "Project id."
type = string
}
variable "service_accounts" {
description = "Service accounts to be created, and roles assigned them on the project."
type = map(list(string))
default = {}
}
variable "service_accounts_additive" {
description = "Service accounts to be created, and roles assigned them on the project additively."
type = map(list(string))
default = {}
}
variable "service_accounts_iam" {
description = "IAM bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}."
type = map(map(list(string)))
default = {}
nullable = false
}
variable "service_accounts_iam_additive" {
description = "IAM additive bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}."
type = map(map(list(string)))
default = {}
nullable = false
}
variable "service_identities_iam" {
description = "Custom IAM settings for service identities in service => [role] format."
type = map(list(string))
default = {}
nullable = false
}
variable "service_identities_iam_additive" {
description = "Custom additive IAM settings for service identities in service => [role] format."
type = map(list(string))
default = {}
nullable = false
}
variable "services" {
description = "Services to be enabled for the project."
type = list(string)
default = []
nullable = false
}
variable "vpc" {
description = "VPC configuration for the project."
type = object({ type = object({
host_project = string contacts = optional(map(list(string)), {})
gke_setup = optional(object({ labels = optional(map(string), {})
enable_security_admin = optional(bool, false) metric_scopes = optional(list(string), [])
enable_host_service_agent = optional(bool, false) service_encryption_key_ids = optional(map(list(string)), {})
}), {}) service_perimeter_bridges = optional(list(string), [])
service_iam_grants = optional(list(string), []) services = optional(list(string), [])
service_identity_iam = optional(map(list(string)), {}) tag_bindings = optional(map(string), {})
subnets_iam = optional(map(list(string)), {}) # non-project resources
service_accounts = optional(map(object({
default_roles = optional(bool, true)
})), {})
})
nullable = false
default = {}
}
variable "data_overrides" {
description = "Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`."
type = object({
billing_account = optional(string)
contacts = optional(map(list(string)))
prefix = optional(string)
service_encryption_key_ids = optional(map(list(string)))
service_perimeter_bridges = optional(list(string))
service_perimeter_standard = optional(string)
tag_bindings = optional(map(string))
services = optional(list(string))
# non-project resources
service_accounts = optional(map(object({
default_roles = optional(bool, true)
})))
})
nullable = false
default = {}
}
variable "factory_data" {
description = "Project data from either YAML files or externally parsed data."
type = object({
data = optional(map(any))
data_path = optional(string)
}) })
default = {
host_project = null
}
nullable = false nullable = false
validation { validation {
condition = var.vpc.host_project != null || ( condition = (
var.vpc.host_project == null && length(var.vpc.gke_setup) == 0 && length(var.vpc.service_iam_grants) == 0 && (var.factory_data.data != null ? 1 : 0) +
length(var.vpc.service_identity_iam) == 0 && length(var.vpc.subnets_iam) == 0 (var.factory_data.data_path != null ? 1 : 0)
) ) == 1
error_message = "host_project is required if providing any additional configuration for vpc" error_message = "One of data or data_path needs to be set."
} }
} }

View File

@ -36,7 +36,7 @@ Once the resources have been created, do the following to verify that everything
kubectl apply -f tenant-setup.yaml kubectl apply -f tenant-setup.yaml
By applying that manifest thw following is created: By applying that manifest the following is created:
* A namespace called "apis". This is the namespace where the application will be deployed. * A namespace called "apis". This is the namespace where the application will be deployed.
* A Role and a RoleBinding in previously created namespace so the service account that has been configured for the CD pipeline trigger in Cloud Build is able to deploy the kubernetes application to that namespace. * A Role and a RoleBinding in previously created namespace so the service account that has been configured for the CD pipeline trigger in Cloud Build is able to deploy the kubernetes application to that namespace.

View File

@ -32,9 +32,11 @@ module "project" {
source = "../../../modules/project" source = "../../../modules/project"
project_create = var.project_create != null project_create = var.project_create != null
billing_account = try(var.project_create.billing_account, null) billing_account = try(var.project_create.billing_account, null)
oslogin = try(var.project_create.oslogin, false) compute_metadata = var.project_create.oslogin != true ? {} : {
parent = try(var.project_create.parent, null) enable-oslogin = "true"
name = var.project_id }
parent = try(var.project_create.parent, null)
name = var.project_id
services = [ services = [
"compute.googleapis.com", "compute.googleapis.com",
"container.googleapis.com" "container.googleapis.com"

View File

@ -41,8 +41,9 @@ module "project-svc-gce" {
prefix = var.prefix prefix = var.prefix
name = "gce" name = "gce"
services = var.project_services services = var.project_services
oslogin = true compute_metadata = {
oslogin_admins = var.owners_gce enable-oslogin = "true"
}
shared_vpc_service_config = { shared_vpc_service_config = {
host_project = module.project-host.project_id host_project = module.project-host.project_id
service_identity_iam = { service_identity_iam = {
@ -50,7 +51,8 @@ module "project-svc-gce" {
} }
} }
iam = { iam = {
"roles/owner" = var.owners_gce "roles/compute.osAdminLogin" = var.owners_gce
"roles/owner" = var.owners_gce
} }
} }

View File

@ -10,7 +10,13 @@ This architecture can be used for the following use cases and more:
* Intranet / internal Wiki * Intranet / internal Wiki
* E-commerce platform * E-commerce platform
# Architecture ## TODO
* [ ] refactor variables merging WP configuration in a single variable
* [ ] optional creation of a remote artifact registry repository
* [ ] optional serverless connector
## Architecture
![Wordpress on Cloud Run](images/architecture.png "Wordpress on Cloud Run") ![Wordpress on Cloud Run](images/architecture.png "Wordpress on Cloud Run")
@ -20,9 +26,7 @@ The main components that are deployed in this architecture are the following (yo
* [Cloud SQL](https://cloud.google.com/sql): Managed solution for SQL databases * [Cloud SQL](https://cloud.google.com/sql): Managed solution for SQL databases
* [VPC Serverless Connector](https://cloud.google.com/vpc/docs/serverless-vpc-access): Solution to access the CloudSQL VPC from Cloud Run, using only internal IP addresses * [VPC Serverless Connector](https://cloud.google.com/vpc/docs/serverless-vpc-access): Solution to access the CloudSQL VPC from Cloud Run, using only internal IP addresses
# Setup ## Setup and deployment
## Prerequisites
### Setting up the project for the deployment ### Setting up the project for the deployment
@ -30,8 +34,6 @@ This example will deploy all its resources into the project defined by the `proj
If `project_create` is left to null, the identity performing the deployment needs the `owner` role on the project defined by the `project_id` variable. Otherwise, the identity performing the deployment needs `resourcemanager.projectCreator` on the resource hierarchy node specified by `project_create.parent` and `billing.user` on the billing account specified by `project_create.billing_account_id`. If `project_create` is left to null, the identity performing the deployment needs the `owner` role on the project defined by the `project_id` variable. Otherwise, the identity performing the deployment needs `resourcemanager.projectCreator` on the resource hierarchy node specified by `project_create.parent` and `billing.user` on the billing account specified by `project_create.billing_account_id`.
## Deployment
### Step 0: Cloning the repository ### Step 0: Cloning the repository
If you want to deploy from your Cloud Shell, click on the image below, sign in if required and when the prompt appears, click on “confirm”. If you want to deploy from your Cloud Shell, click on the image below, sign in if required and when the prompt appears, click on “confirm”.
@ -116,20 +118,19 @@ terraform destroy
The above command will delete the associated resources so there will be no billable charges made afterwards. The above command will delete the associated resources so there will be no billable charges made afterwards.
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Variables ## Variables
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [prefix](variables.tf#L57) | Prefix used for resource names. | <code>string</code> | ✓ | | | [prefix](variables.tf#L63) | Prefix used for resource names. | <code>string</code> | ✓ | |
| [project_id](variables.tf#L81) | Project id, references existing project if `project_create` is null. | <code>string</code> | ✓ | | | [project_id](variables.tf#L81) | Project id, references existing project if `project_create` is null. | <code>string</code> | ✓ | |
| [wordpress_image](variables.tf#L92) | Image to run with Cloud Run, starts with \"gcr.io\". | <code>string</code> | ✓ | | | [wordpress_image](variables.tf#L92) | Image to run with Cloud Run, starts with \"gcr.io\". | <code>string</code> | ✓ | |
| [cloud_run_invoker](variables.tf#L18) | IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone). | <code>string</code> | | <code>&#34;allUsers&#34;</code> | | [admin_principal](variables.tf#L17) | User or group that is assigned roles, in IAM format (`group:foo@example.com`). | <code>string</code> | | <code>null</code> |
| [cloudsql_password](variables.tf#L24) | CloudSQL password (will be randomly generated by default). | <code>string</code> | | <code>null</code> | | [cloud_run_invoker](variables.tf#L24) | IAM member authorized to access the end-point (for example, 'user:YOUR_IAM_USER' for only you or 'allUsers' for everyone). | <code>string</code> | | <code>&#34;allUsers&#34;</code> |
| [connector](variables.tf#L30) | Existing VPC serverless connector to use if not creating a new one. | <code>string</code> | | <code>null</code> | | [cloudsql_password](variables.tf#L30) | CloudSQL password (will be randomly generated by default). | <code>string</code> | | <code>null</code> |
| [create_connector](variables.tf#L36) | Should a VPC serverless connector be created or not. | <code>bool</code> | | <code>true</code> | | [connector](variables.tf#L36) | Existing VPC serverless connector to use if not creating a new one. | <code>string</code> | | <code>null</code> |
| [ip_ranges](variables.tf#L43) | CIDR blocks: VPC serverless connector, Private Service Access(PSA) for CloudSQL, CloudSQL VPC. | <code title="object&#40;&#123;&#10; connector &#61; string&#10; psa &#61; string&#10; sql_vpc &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; connector &#61; &#34;10.8.0.0&#47;28&#34;&#10; psa &#61; &#34;10.60.0.0&#47;24&#34;&#10; sql_vpc &#61; &#34;10.0.0.0&#47;20&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | | [create_connector](variables.tf#L42) | Should a VPC serverless connector be created or not. | <code>bool</code> | | <code>true</code> |
| [principals](variables.tf#L66) | List of users to give rights to (CloudSQL admin, client and instanceUser, Logging admin, Service Account User and TokenCreator), eg 'user@domain.com'. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [ip_ranges](variables.tf#L49) | CIDR blocks: VPC serverless connector, Private Service Access(PSA) for CloudSQL, CloudSQL VPC. | <code title="object&#40;&#123;&#10; connector &#61; string&#10; psa &#61; string&#10; sql_vpc &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; connector &#61; &#34;10.8.0.0&#47;28&#34;&#10; psa &#61; &#34;10.60.0.0&#47;24&#34;&#10; sql_vpc &#61; &#34;10.0.0.0&#47;20&#34;&#10;&#125;">&#123;&#8230;&#125;</code> |
| [project_create](variables.tf#L72) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [project_create](variables.tf#L72) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [region](variables.tf#L86) | Region for the created resources. | <code>string</code> | | <code>&#34;europe-west4&#34;</code> | | [region](variables.tf#L86) | Region for the created resources. | <code>string</code> | | <code>&#34;europe-west4&#34;</code> |
| [wordpress_password](variables.tf#L97) | Password for the Wordpress user (will be randomly generated by default). | <code>string</code> | | <code>null</code> | | [wordpress_password](variables.tf#L97) | Password for the Wordpress user (will be randomly generated by default). | <code>string</code> | | <code>null</code> |
@ -143,5 +144,20 @@ The above command will delete the associated resources so there will be no billa
| [cloudsql_password](outputs.tf#L23) | CloudSQL password. | ✓ | | [cloudsql_password](outputs.tf#L23) | CloudSQL password. | ✓ |
| [wp_password](outputs.tf#L29) | Wordpress user password. | ✓ | | [wp_password](outputs.tf#L29) | Wordpress user password. | ✓ |
| [wp_user](outputs.tf#L35) | Wordpress username. | | | [wp_user](outputs.tf#L35) | Wordpress username. | |
<!-- END TFDOC --> <!-- END TFDOC -->
## Test
```hcl
module "test" {
source = "./fabric/blueprints/third-party-solutions/wordpress/cloudrun"
admin_principal = "group:foo@example.com"
prefix = "wp-cr-test"
project_create = {
billing_account_id = "1234-ABCD-1234"
parent = "folders/1234563"
}
project_id = "test-prj"
wordpress_image = "gcr.io/myprj/wordpress"
}
# tftest modules=5 resources=33
```

View File

@ -16,30 +16,29 @@
locals { locals {
all_principals_iam = [for k in var.principals : "user:${k}"]
cloudsql_conf = { cloudsql_conf = {
database_version = "MYSQL_8_0" database_version = "MYSQL_8_0"
tier = "db-g1-small" tier = "db-g1-small"
db = "wp-mysql" db = "wp-mysql"
user = "admin" user = "admin"
} }
iam = { iam_roles = [
# CloudSQL "roles/cloudsql.admin",
"roles/cloudsql.admin" = local.all_principals_iam "roles/cloudsql.client",
"roles/cloudsql.client" = local.all_principals_iam "roles/cloudsql.instanceUser",
"roles/cloudsql.instanceUser" = local.all_principals_iam "roles/logging.admin",
# common roles "roles/iam.serviceAccountUser",
"roles/logging.admin" = local.all_principals_iam "roles/iam.serviceAccountTokenCreator"
"roles/iam.serviceAccountUser" = local.all_principals_iam ]
"roles/iam.serviceAccountTokenCreator" = local.all_principals_iam
}
connector = var.connector == null ? google_vpc_access_connector.connector.0.self_link : var.connector connector = var.connector == null ? google_vpc_access_connector.connector.0.self_link : var.connector
wp_user = "user" wp_user = "user"
wp_pass = var.wordpress_password == null ? random_password.wp_password.result : var.wordpress_password wp_pass = var.wordpress_password == null ? random_password.wp_password.result : var.wordpress_password
} }
resource "random_password" "wp_password" {
length = 8
}
# either create a project or set up the given one
module "project" { module "project" {
source = "../../../../modules/project" source = "../../../../modules/project"
name = var.project_id name = var.project_id
@ -47,8 +46,19 @@ module "project" {
billing_account = try(var.project_create.billing_account_id, null) billing_account = try(var.project_create.billing_account_id, null)
project_create = var.project_create != null project_create = var.project_create != null
prefix = var.project_create == null ? null : var.prefix prefix = var.project_create == null ? null : var.prefix
iam = var.project_create != null ? local.iam : {} iam = (
iam_additive = var.project_create == null ? local.iam : {} var.project_create != true || var.admin_principal == null
? {}
: { for r in local.iam_roles : r => [var.admin_principal] }
)
iam_bindings_additive = (
var.project_create == true || var.admin_principal == null
? {}
: { for r in local.iam_roles : r => {
member = var.admin_principal
role = r
} }
)
services = [ services = [
"run.googleapis.com", "run.googleapis.com",
"logging.googleapis.com", "logging.googleapis.com",
@ -60,60 +70,45 @@ module "project" {
] ]
} }
resource "random_password" "wp_password" {
length = 8
}
# create the Cloud Run service
module "cloud_run" { module "cloud_run" {
source = "../../../../modules/cloud-run" source = "../../../../modules/cloud-run"
project_id = module.project.project_id project_id = module.project.project_id
name = "${var.prefix}-cr-wordpress" name = "${var.prefix}-cr-wordpress"
region = var.region region = var.region
containers = {
containers = [{ wp = {
image = var.wordpress_image image = var.wordpress_image
ports = [{ ports = {
name = "http1" http = { container_port = var.wordpress_port }
protocol = null }
container_port = var.wordpress_port
}]
options = {
command = null
args = null
env_from = null
# set up the database connection
env = { env = {
"APACHE_HTTP_PORT_NUMBER" : var.wordpress_port APACHE_HTTP_PORT_NUMBER = var.wordpress_port
"WORDPRESS_DATABASE_HOST" : module.cloudsql.ip WORDPRESS_DATABASE_HOST = module.cloudsql.ip
"WORDPRESS_DATABASE_NAME" : local.cloudsql_conf.db WORDPRESS_DATABASE_NAME = local.cloudsql_conf.db
"WORDPRESS_DATABASE_USER" : local.cloudsql_conf.user WORDPRESS_DATABASE_USER = local.cloudsql_conf.user
"WORDPRESS_DATABASE_PASSWORD" : var.cloudsql_password == null ? module.cloudsql.user_passwords[local.cloudsql_conf.user] : var.cloudsql_password WORDPRESS_DATABASE_PASSWORD = (
"WORDPRESS_USERNAME" : local.wp_user var.cloudsql_password == null
"WORDPRESS_PASSWORD" : local.wp_pass ? module.cloudsql.user_passwords[local.cloudsql_conf.user]
: var.cloudsql_password
)
WORDPRESS_USERNAME = local.wp_user
WORDPRESS_PASSWORD = local.wp_pass
} }
} }
resources = null }
volume_mounts = null
}]
iam = { iam = {
"roles/run.invoker" : [var.cloud_run_invoker] "roles/run.invoker" : [var.cloud_run_invoker]
} }
revision_annotations = { revision_annotations = {
autoscaling = { autoscaling = {
min_scale = 1 min_scale = 1
max_scale = 2 max_scale = 2
} }
# connect to CloudSQL # connect to CloudSQL
cloudsql_instances = [module.cloudsql.connection_name] cloudsql_instances = [module.cloudsql.connection_name]
vpcaccess_connector = null
# allow all traffic # allow all traffic
vpcaccess_egress = "all-traffic"
vpcaccess_connector = local.connector vpcaccess_connector = local.connector
vpcaccess_egress = "all-traffic"
} }
ingress_settings = "all" ingress_settings = "all"
} }

View File

@ -14,6 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
variable "admin_principal" {
description = "User or group that is assigned roles, in IAM format (`group:foo@example.com`)."
type = string
default = null
}
# Documentation: https://cloud.google.com/run/docs/securing/managing-access#making_a_service_public # Documentation: https://cloud.google.com/run/docs/securing/managing-access#making_a_service_public
variable "cloud_run_invoker" { variable "cloud_run_invoker" {
type = string type = string
@ -63,12 +69,6 @@ variable "prefix" {
} }
} }
variable "principals" {
description = "List of users to give rights to (CloudSQL admin, client and instanceUser, Logging admin, Service Account User and TokenCreator), eg 'user@domain.com'."
type = list(string)
default = []
}
variable "project_create" { 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." description = "Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format."
type = object({ type = object({

View File

@ -1,4 +1,4 @@
# Copyright 2022 Google LLC # Copyright 2023 Google LLC
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -13,8 +13,6 @@
# limitations under the License. # limitations under the License.
default: default:
before_script:
- echo "$${CI_JOB_JWT_V2}" > token.txt
image: image:
name: hashicorp/terraform name: hashicorp/terraform
entrypoint: entrypoint:
@ -27,7 +25,9 @@ variables:
FAST_SERVICE_ACCOUNT: ${service_account} FAST_SERVICE_ACCOUNT: ${service_account}
FAST_WIF_PROVIDER: ${identity_provider} FAST_WIF_PROVIDER: ${identity_provider}
SSH_AUTH_SOCK: /tmp/ssh_agent.sock SSH_AUTH_SOCK: /tmp/ssh_agent.sock
%{~ if tf_providers_file != "" ~}
TF_PROVIDERS_FILE: ${tf_providers_file} TF_PROVIDERS_FILE: ${tf_providers_file}
%{~ endif ~}
TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)}
stages: stages:
@ -40,13 +40,26 @@ cache:
key: gcp-auth key: gcp-auth
paths: paths:
- cicd-sa-credentials.json - cicd-sa-credentials.json
- .tf-setup - token.txt
%{~ if tf_providers_file != "" ~}
- ${tf_providers_file}
%{~ endif ~}
%{~ for f in tf_var_files ~}
- ${f}
%{~ endfor ~}
gcp-auth: gcp-auth:
id_tokens:
GITLAB_TOKEN:
aud:
%{~ for aud in audiences ~}
- ${aud}
%{~ endfor ~}
image: image:
name: google/cloud-sdk:slim name: google/cloud-sdk:slim
stage: gcp-auth stage: gcp-auth
script: script:
- echo "$${GITLAB_TOKEN}" > token.txt
- | - |
gcloud iam workload-identity-pools create-cred-config \ gcloud iam workload-identity-pools create-cred-config \
$${FAST_WIF_PROVIDER} \ $${FAST_WIF_PROVIDER} \
@ -54,6 +67,7 @@ gcp-auth:
--service-account-token-lifetime-seconds=3600 \ --service-account-token-lifetime-seconds=3600 \
--output-file=$${GOOGLE_CREDENTIALS} \ --output-file=$${GOOGLE_CREDENTIALS} \
--credential-source-file=token.txt --credential-source-file=token.txt
tf-files: tf-files:
dependencies: dependencies:
- gcp-auth - gcp-auth
@ -63,15 +77,18 @@ tf-files:
script: script:
# - gcloud components install -q alpha # - gcloud components install -q alpha
- gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS} - gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS}
- mkdir -p .tf-setup %{~ if tf_providers_file != "" ~}
- | - gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/providers/${tf_providers_file}" ./
gcloud alpha storage cp -r \ %{~ endif ~}
"gs://$${FAST_OUTPUTS_BUCKET}/providers/$${TF_PROVIDERS_FILE}" .tf-setup/ %{~ for f in tf_var_files ~}
- | - gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/tfvars/${f}" ./
gcloud alpha storage cp -r \ %{~ endfor ~}
"gs://$${FAST_OUTPUTS_BUCKET}/tfvars" .tf-setup/ - ls -l
tf-plan: tf-plan:
dependencies:
- tf-files
stage: tf-plan
# uncomment the following lines and set the SSH key secret for private modules repo # uncomment the following lines and set the SSH key secret for private modules repo
# before_script: # before_script:
# - | # - |
@ -80,20 +97,15 @@ tf-plan:
# mkdir -p ~/.ssh # mkdir -p ~/.ssh
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
stage: tf-plan
script: script:
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
- |
for f in $${TF_VAR_FILES}; do
ln -s ".tf-setup/tfvars/$f" ./
done
- terraform init - terraform init
- terraform validate - terraform validate
- terraform plan - terraform plan
dependencies:
- tf-files
tf-apply: tf-apply:
dependencies:
- tf-files
stage: tf-apply
# uncomment the following lines and set the SSH key secret for private modules repo # uncomment the following lines and set the SSH key secret for private modules repo
# before_script: # before_script:
# - | # - |
@ -102,18 +114,10 @@ tf-apply:
# mkdir -p ~/.ssh # mkdir -p ~/.ssh
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
stage: tf-apply
script: script:
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
- |
for f in $${TF_VAR_FILES}; do
ln -s ".tf-setup/tfvars/$f" ./
done
- terraform init - terraform init
- terraform validate - terraform validate
- terraform apply -input=false -auto-approve - terraform apply -input=false -auto-approve
dependencies:
- tf-files
when: manual when: manual
only: only:
variables: variables:

View File

@ -175,7 +175,6 @@ This configuration is possible but unsupported and only exists for development p
<!-- TFDOC OPTS files:1 show_extra:1 --> <!-- TFDOC OPTS files:1 show_extra:1 -->
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Files ## Files
| name | description | modules | resources | | name | description | modules | resources |
@ -187,7 +186,7 @@ This configuration is possible but unsupported and only exists for development p
| [identity-providers.tf](./identity-providers.tf) | Workload Identity Federation provider definitions. | | <code>google_iam_workload_identity_pool</code> · <code>google_iam_workload_identity_pool_provider</code> | | [identity-providers.tf](./identity-providers.tf) | Workload Identity Federation provider definitions. | | <code>google_iam_workload_identity_pool</code> · <code>google_iam_workload_identity_pool_provider</code> |
| [log-export.tf](./log-export.tf) | Audit log project and sink. | <code>bigquery-dataset</code> · <code>gcs</code> · <code>logging-bucket</code> · <code>project</code> · <code>pubsub</code> | | | [log-export.tf](./log-export.tf) | Audit log project and sink. | <code>bigquery-dataset</code> · <code>gcs</code> · <code>logging-bucket</code> · <code>project</code> · <code>pubsub</code> | |
| [main.tf](./main.tf) | Module-level locals and resources. | <code>folder</code> | | | [main.tf](./main.tf) | Module-level locals and resources. | <code>folder</code> | |
| [organization.tf](./organization.tf) | Organization tag and conditional IAM grant. | <code>organization</code> | <code>google_organization_iam_member</code> · <code>google_tags_tag_value_iam_member</code> | | [organization.tf](./organization.tf) | Organization tag and conditional IAM grant. | <code>organization</code> | <code>google_tags_tag_value_iam_member</code> |
| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | <code>local_file</code> | | [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | <code>local_file</code> |
| [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | <code>google_storage_bucket_object</code> | | [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | <code>google_storage_bucket_object</code> |
| [outputs.tf](./outputs.tf) | Module outputs. | | | | [outputs.tf](./outputs.tf) | Module outputs. | | |
@ -197,35 +196,35 @@ This configuration is possible but unsupported and only exists for development p
| name | description | type | required | default | producer | | name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|:---:|
| [automation](variables.tf#L20) | Automation resources created by the organization-level bootstrap stage. | <code title="object&#40;&#123;&#10; outputs_bucket &#61; string&#10; project_id &#61; string&#10; project_number &#61; string&#10; federated_identity_pool &#61; string&#10; federated_identity_providers &#61; map&#40;object&#40;&#123;&#10; issuer &#61; string&#10; issuer_uri &#61; string&#10; name &#61; string&#10; principal_tpl &#61; string&#10; principalset_tpl &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> | | [automation](variables.tf#L20) | Automation resources created by the organization-level bootstrap stage. | <code title="object&#40;&#123;&#10; outputs_bucket &#61; string&#10; project_id &#61; string&#10; project_number &#61; string&#10; federated_identity_pool &#61; string&#10; federated_identity_providers &#61; map&#40;object&#40;&#123;&#10; audiences &#61; list&#40;string&#41;&#10; issuer &#61; string&#10; issuer_uri &#61; string&#10; name &#61; string&#10; principal_tpl &#61; string&#10; principalset_tpl &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> |
| [billing_account](variables.tf#L38) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | <code title="object&#40;&#123;&#10; id &#61; string&#10; is_org_level &#61; optional&#40;bool, true&#41;&#10; no_iam &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | | [billing_account](variables.tf#L39) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | <code title="object&#40;&#123;&#10; id &#61; string&#10; is_org_level &#61; optional&#40;bool, true&#41;&#10; no_iam &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | |
| [organization](variables.tf#L191) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; number&#10; customer_id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> | | [organization](variables.tf#L214) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; number&#10; customer_id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> |
| [prefix](variables.tf#L207) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>0-bootstrap</code> | | [prefix](variables.tf#L230) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>0-bootstrap</code> |
| [tag_keys](variables.tf#L230) | Organization tag keys. | <code title="object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10; tenant &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>1-resman</code> | | [tag_keys](variables.tf#L253) | Organization tag keys. | <code title="object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10; tenant &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>1-resman</code> |
| [tag_names](variables.tf#L241) | Customized names for resource management tags. | <code title="object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10; tenant &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>1-resman</code> | | [tag_names](variables.tf#L264) | Customized names for resource management tags. | <code title="object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10; tenant &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>1-resman</code> |
| [tag_values](variables.tf#L252) | Organization resource management tag values. | <code>map&#40;string&#41;</code> | ✓ | | <code>1-resman</code> | | [tag_values](variables.tf#L275) | Organization resource management tag values. | <code>map&#40;string&#41;</code> | ✓ | | <code>1-resman</code> |
| [tenant_config](variables.tf#L259) | Tenant configuration. Short name must be 4 characters or less. If `short_name_is_prefix` is true, short name must be 9 characters or less, and will be used as the prefix as is. | <code title="object&#40;&#123;&#10; descriptive_name &#61; string&#10; groups &#61; object&#40;&#123;&#10; gcp-admins &#61; string&#10; gcp-devops &#61; optional&#40;string&#41;&#10; gcp-network-admins &#61; optional&#40;string&#41;&#10; gcp-security-admins &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; short_name &#61; string&#10; short_name_is_prefix &#61; optional&#40;bool, false&#41;&#10; fast_features &#61; optional&#40;object&#40;&#123;&#10; data_platform &#61; optional&#40;bool&#41;&#10; gke &#61; optional&#40;bool&#41;&#10; project_factory &#61; optional&#40;bool&#41;&#10; sandbox &#61; optional&#40;bool&#41;&#10; teams &#61; optional&#40;bool&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; locations &#61; optional&#40;object&#40;&#123;&#10; bq &#61; optional&#40;string&#41;&#10; gcs &#61; optional&#40;string&#41;&#10; logging &#61; optional&#40;string&#41;&#10; pubsub &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | | [tenant_config](variables.tf#L282) | Tenant configuration. Short name must be 4 characters or less. If `short_name_is_prefix` is true, short name must be 9 characters or less, and will be used as the prefix as is. | <code title="object&#40;&#123;&#10; descriptive_name &#61; string&#10; groups &#61; object&#40;&#123;&#10; gcp-admins &#61; string&#10; gcp-devops &#61; optional&#40;string&#41;&#10; gcp-network-admins &#61; optional&#40;string&#41;&#10; gcp-security-admins &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; short_name &#61; string&#10; short_name_is_prefix &#61; optional&#40;bool, false&#41;&#10; fast_features &#61; optional&#40;object&#40;&#123;&#10; data_platform &#61; optional&#40;bool&#41;&#10; gke &#61; optional&#40;bool&#41;&#10; project_factory &#61; optional&#40;bool&#41;&#10; sandbox &#61; optional&#40;bool&#41;&#10; teams &#61; optional&#40;bool&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; locations &#61; optional&#40;object&#40;&#123;&#10; bq &#61; optional&#40;string&#41;&#10; gcs &#61; optional&#40;string&#41;&#10; logging &#61; optional&#40;string&#41;&#10; pubsub &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | |
| [cicd_repositories](variables.tf#L48) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | <code title="object&#40;&#123;&#10; bootstrap &#61; optional&#40;object&#40;&#123;&#10; branch &#61; optional&#40;string&#41;&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#41;&#10; resman &#61; optional&#40;object&#40;&#123;&#10; branch &#61; optional&#40;string&#41;&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | | [cicd_repositories](variables.tf#L49) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | <code title="object&#40;&#123;&#10; bootstrap &#61; optional&#40;object&#40;&#123;&#10; branch &#61; optional&#40;string&#41;&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#41;&#10; resman &#61; optional&#40;object&#40;&#123;&#10; branch &#61; optional&#40;string&#41;&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | |
| [custom_roles](variables.tf#L94) | Custom roles defined at the organization level, in key => id format. | <code title="object&#40;&#123;&#10; service_project_network_admin &#61; string&#10; tenant_network_admin &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | <code>0-bootstrap</code> | | [custom_roles](variables.tf#L95) | Custom roles defined at the organization level, in key => id format. | <code title="object&#40;&#123;&#10; service_project_network_admin &#61; string&#10; tenant_network_admin &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | <code>0-bootstrap</code> |
| [fast_features](variables.tf#L104) | Selective control for top-level FAST features. | <code title="object&#40;&#123;&#10; data_platform &#61; optional&#40;bool, true&#41;&#10; gke &#61; optional&#40;bool, true&#41;&#10; project_factory &#61; optional&#40;bool, true&#41;&#10; sandbox &#61; optional&#40;bool, true&#41;&#10; teams &#61; optional&#40;bool, true&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | <code>0-bootstrap</code> | | [fast_features](variables.tf#L105) | Selective control for top-level FAST features. | <code title="object&#40;&#123;&#10; data_platform &#61; optional&#40;bool, true&#41;&#10; gke &#61; optional&#40;bool, true&#41;&#10; project_factory &#61; optional&#40;bool, true&#41;&#10; sandbox &#61; optional&#40;bool, true&#41;&#10; teams &#61; optional&#40;bool, true&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | <code>0-bootstrap</code> |
| [federated_identity_providers](variables.tf#L118) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | <code title="map&#40;object&#40;&#123;&#10; attribute_condition &#61; string&#10; issuer &#61; string&#10; custom_settings &#61; object&#40;&#123;&#10; issuer_uri &#61; string&#10; allowed_audiences &#61; list&#40;string&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | | [federated_identity_providers](variables.tf#L119) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | <code title="map&#40;object&#40;&#123;&#10; attribute_condition &#61; optional&#40;string&#41;&#10; issuer &#61; string&#10; custom_settings &#61; optional&#40;object&#40;&#123;&#10; issuer_uri &#61; optional&#40;string&#41;&#10; audiences &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [group_iam](variables.tf#L132) | Tenant-level custom group IAM settings in group => [roles] format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | | [group_iam](variables.tf#L133) | Tenant-level custom group IAM settings in group => [roles] format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [iam](variables.tf#L138) | Tenant-level custom IAM settings in role => [principal] format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | | [groups](variables.tf#L139) | Group names or emails to grant organization-level permissions. If just the name is provided, the default organization domain is assumed. | <code title="object&#40;&#123;&#10; gcp-devops &#61; optional&#40;string&#41;&#10; gcp-network-admins &#61; optional&#40;string&#41;&#10; gcp-security-admins &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | <code>0-bootstrap</code> |
| [iam_additive](variables.tf#L144) | Tenant-level custom IAM settings in role => [principal] format for non-authoritative bindings. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | | [iam](variables.tf#L152) | Tenant-level custom IAM settings in role => [principal] format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [locations](variables.tf#L150) | Optional locations for GCS, BigQuery, and logging buckets created here. These are the defaults set at the organization level, and can be overridden via the tenant config variable. | <code title="object&#40;&#123;&#10; bq &#61; string&#10; gcs &#61; string&#10; logging &#61; string&#10; pubsub &#61; list&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; bq &#61; &#34;EU&#34;&#10; gcs &#61; &#34;EU&#34;&#10; logging &#61; &#34;global&#34;&#10; pubsub &#61; &#91;&#93;&#10;&#125;">&#123;&#8230;&#125;</code> | <code>0-bootstrap</code> | | [iam_bindings_additive](variables.tf#L158) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [log_sinks](variables.tf#L170) | Tenant-level log sinks, in name => {type, filter} format. | <code title="map&#40;object&#40;&#123;&#10; filter &#61; string&#10; type &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code title="&#123;&#10; audit-logs &#61; &#123;&#10; filter &#61; &#34;logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Factivity&#92;&#34; OR logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Fsystem_event&#92;&#34;&#34;&#10; type &#61; &#34;logging&#34;&#10; &#125;&#10;&#125;">&#123;&#8230;&#125;</code> | | | [locations](variables.tf#L173) | Optional locations for GCS, BigQuery, and logging buckets created here. These are the defaults set at the organization level, and can be overridden via the tenant config variable. | <code title="object&#40;&#123;&#10; bq &#61; string&#10; gcs &#61; string&#10; logging &#61; string&#10; pubsub &#61; list&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; bq &#61; &#34;EU&#34;&#10; gcs &#61; &#34;EU&#34;&#10; logging &#61; &#34;global&#34;&#10; pubsub &#61; &#91;&#93;&#10;&#125;">&#123;&#8230;&#125;</code> | <code>0-bootstrap</code> |
| [outputs_location](variables.tf#L201) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | | | [log_sinks](variables.tf#L193) | Tenant-level log sinks, in name => {type, filter} format. | <code title="map&#40;object&#40;&#123;&#10; filter &#61; string&#10; type &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code title="&#123;&#10; audit-logs &#61; &#123;&#10; filter &#61; &#34;logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Factivity&#92;&#34; OR logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Fsystem_event&#92;&#34;&#34;&#10; type &#61; &#34;logging&#34;&#10; &#125;&#10;&#125;">&#123;&#8230;&#125;</code> | |
| [project_parent_ids](variables.tf#L217) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the tenant folder as parent. | <code title="object&#40;&#123;&#10; automation &#61; string&#10; logging &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; automation &#61; null&#10; logging &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> | | | [outputs_location](variables.tf#L224) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | |
| [test_principal](variables.tf#L300) | Used when testing to bypass the data source returning the current identity. | <code>string</code> | | <code>null</code> | | | [project_parent_ids](variables.tf#L240) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the tenant folder as parent. | <code title="object&#40;&#123;&#10; automation &#61; string&#10; logging &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; automation &#61; null&#10; logging &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> | |
| [test_principal](variables.tf#L323) | Used when testing to bypass the data source returning the current identity. | <code>string</code> | | <code>null</code> | |
## Outputs ## Outputs
| name | description | sensitive | consumers | | name | description | sensitive | consumers |
|---|---|:---:|---| |---|---|:---:|---|
| [cicd_workflows](outputs.tf#L107) | CI/CD workflows for tenant bootstrap and resource management stages. | ✓ | | | [cicd_workflows](outputs.tf#L109) | CI/CD workflows for tenant bootstrap and resource management stages. | ✓ | |
| [federated_identity](outputs.tf#L113) | Workload Identity Federation pool and providers. | | | | [federated_identity](outputs.tf#L115) | Workload Identity Federation pool and providers. | | |
| [provider](outputs.tf#L123) | Terraform provider file for tenant resource management stage. | ✓ | <code>stage-01</code> | | [provider](outputs.tf#L125) | Terraform provider file for tenant resource management stage. | ✓ | <code>stage-01</code> |
| [tenant_resources](outputs.tf#L130) | Tenant-level resources. | | | | [tenant_resources](outputs.tf#L132) | Tenant-level resources. | | |
| [tfvars](outputs.tf#L141) | Terraform variable files for the following tenant stages. | ✓ | | | [tfvars](outputs.tf#L143) | Terraform variable files for the following tenant stages. | ✓ | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -106,6 +106,7 @@ module "automation-tf-resman-sa-stage2-3" {
} }
# assign org policy admin with a tag-based condition to stage 2 and 3 SAs # assign org policy admin with a tag-based condition to stage 2 and 3 SAs
# TODO: move to new iam_bindings_additive in the organization module
resource "google_organization_iam_member" "org_policy_admin_stage2_3" { resource "google_organization_iam_member" "org_policy_admin_stage2_3" {
for_each = { for_each = {

View File

@ -20,23 +20,27 @@ locals {
_file_prefix = "tenants/${var.tenant_config.short_name}" _file_prefix = "tenants/${var.tenant_config.short_name}"
# derive identity pool names from identity providers for easy reference # derive identity pool names from identity providers for easy reference
cicd_identity_pools = { cicd_identity_pools = {
for k, v in local.cicd_identity_providers : for k, v in local.cicd_providers :
k => split("/providers/", v.name)[0] k => split("/providers/", v.name)[0]
} }
# merge org-level and tenant-level identity providers # merge org-level and tenant-level identity providers
cicd_identity_providers = merge( cicd_providers = merge(
var.automation.federated_identity_providers, var.automation.federated_identity_providers,
{ {
for k, v in google_iam_workload_identity_pool_provider.default : for k, v in google_iam_workload_identity_pool_provider.default :
k => { k => {
audiences = concat(
v.oidc[0].allowed_audiences,
["https://iam.googleapis.com/${v.name}"]
)
issuer = local.identity_providers[k].issuer issuer = local.identity_providers[k].issuer
issuer_uri = local.identity_providers[k].issuer_uri issuer_uri = try(v.oidc[0].issuer_uri, null)
name = v.name name = v.name
principal_tpl = local.identity_providers[k].principal_tpl principal_tpl = local.identity_providers[k].principal_tpl
principalset_tpl = local.identity_providers[k].principalset_tpl principalset_tpl = local.identity_providers[k].principalset_tpl
} }
}) }
# filter CI/CD repositories to only keep valid ones )
cicd_repositories = { cicd_repositories = {
for k, v in coalesce(var.cicd_repositories, {}) : k => v for k, v in coalesce(var.cicd_repositories, {}) : k => v
if( if(
@ -46,7 +50,7 @@ locals {
try(v.type, null) == "sourcerepo" try(v.type, null) == "sourcerepo"
|| ||
contains( contains(
keys(local.cicd_identity_providers), keys(local.cicd_providers),
coalesce(try(v.identity_provider, null), ":") coalesce(try(v.identity_provider, null), ":")
) )
) )
@ -56,6 +60,9 @@ locals {
) )
) )
} }
cicd_sa_resman = try(
module.automation-tf-cicd-sa-bootstrap["0"].iam_email, null
)
} }
# tenant bootstrap runs in the org scope and uses top-level automation project # tenant bootstrap runs in the org scope and uses top-level automation project
@ -111,12 +118,12 @@ module "automation-tf-cicd-sa-bootstrap" {
"roles/iam.workloadIdentityUser" = [ "roles/iam.workloadIdentityUser" = [
each.value.branch == null each.value.branch == null
? format( ? format(
local.cicd_identity_providers[each.value.identity_provider].principalset_tpl, local.cicd_providers[each.value.identity_provider].principalset_tpl,
local.cicd_identity_pools[each.value.identity_provider], local.cicd_identity_pools[each.value.identity_provider],
each.value.name each.value.name
) )
: format( : format(
local.cicd_identity_providers[each.value.identity_provider].principal_tpl, local.cicd_providers[each.value.identity_provider].principal_tpl,
local.cicd_identity_pools[each.value.identity_provider], local.cicd_identity_pools[each.value.identity_provider],
each.value.name, each.value.name,
each.value.branch each.value.branch
@ -141,10 +148,11 @@ module "automation-tf-org-resman-sa" {
project_id = var.automation.project_id project_id = var.automation.project_id
name = local.resman_sa name = local.resman_sa
service_account_create = false service_account_create = false
iam_additive = { iam_bindings_additive = local.cicd_sa_resman == null ? {} : {
"roles/iam.serviceAccountTokenCreator" = compact([ sa_resman_cicd = {
try(module.automation-tf-cicd-sa-bootstrap["0"].iam_email, null) member = local.cicd_sa_resman
]) role = "roles/iam.serviceAccountTokenCreator"
}
} }
} }
@ -201,12 +209,12 @@ module "automation-tf-cicd-sa-resman" {
"roles/iam.workloadIdentityUser" = [ "roles/iam.workloadIdentityUser" = [
each.value.branch == null each.value.branch == null
? format( ? format(
local.cicd_identity_providers[each.value.identity_provider].principalset_tpl, local.cicd_providers[each.value.identity_provider].principalset_tpl,
local.cicd_identity_pools[each.value.identity_provider], local.cicd_identity_pools[each.value.identity_provider],
each.value.name each.value.name
) )
: format( : format(
local.cicd_identity_providers[each.value.identity_provider].principal_tpl, local.cicd_providers[each.value.identity_provider].principal_tpl,
local.cicd_identity_pools[each.value.identity_provider], local.cicd_identity_pools[each.value.identity_provider],
each.value.name, each.value.name,
each.value.branch each.value.branch

View File

@ -38,7 +38,7 @@ locals {
principal_tpl = "principal://iam.googleapis.com/%s/subject/repo:%s:ref:refs/heads/%s" principal_tpl = "principal://iam.googleapis.com/%s/subject/repo:%s:ref:refs/heads/%s"
principalset_tpl = "principalSet://iam.googleapis.com/%s/attribute.repository/%s" principalset_tpl = "principalSet://iam.googleapis.com/%s/attribute.repository/%s"
} }
# https://docs.gitlab.com/ee/ci/cloud_services/index.html#how-it-works # https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html#token-payload
gitlab = { gitlab = {
attribute_mapping = { attribute_mapping = {
"google.subject" = "assertion.sub" "google.subject" = "assertion.sub"
@ -56,10 +56,9 @@ locals {
"attribute.ref_protected" = "assertion.ref_protected" "attribute.ref_protected" = "assertion.ref_protected"
"attribute.ref_type" = "assertion.ref_type" "attribute.ref_type" = "assertion.ref_type"
} }
allowed_audiences = ["https://gitlab.com"] issuer_uri = "https://gitlab.com"
issuer_uri = "https://gitlab.com" principal_tpl = "principalSet://iam.googleapis.com/%s/attribute.sub/project_path:%s:ref_type:branch:ref:%s"
principal_tpl = "principalSet://iam.googleapis.com/%s/attribute.sub/project_path:%s:ref_type:branch:ref:%s" principalset_tpl = "principalSet://iam.googleapis.com/%s/attribute.repository/%s"
principalset_tpl = "principalSet://iam.googleapis.com/%s/attribute.repository/%s"
} }
} }
} }
@ -82,13 +81,11 @@ resource "google_iam_workload_identity_pool_provider" "default" {
attribute_condition = each.value.attribute_condition attribute_condition = each.value.attribute_condition
attribute_mapping = each.value.attribute_mapping attribute_mapping = each.value.attribute_mapping
oidc { oidc {
allowed_audiences = ( # Setting an empty list configures allowed_audiences to the url of the provider
try(each.value.custom_settings.allowed_audiences, null) != null allowed_audiences = each.value.custom_settings.audiences
? each.value.custom_settings.allowed_audiences # If users don't provide an issuer_uri, we set the public one for the plaform choosed.
: try(each.value.allowed_audiences, null)
)
issuer_uri = ( issuer_uri = (
try(each.value.custom_settings.issuer_uri, null) != null each.value.custom_settings.issuer_uri != null
? each.value.custom_settings.issuer_uri ? each.value.custom_settings.issuer_uri
: try(each.value.issuer_uri, null) : try(each.value.issuer_uri, null)
) )

View File

@ -95,6 +95,6 @@ module "tenant-folder-iam" {
module.automation-tf-resman-sa.iam_email module.automation-tf-resman-sa.iam_email
] ]
}) })
iam_additive = var.iam_additive iam_bindings_additive = var.iam_bindings_additive
depends_on = [module.automation-project] depends_on = [module.automation-project]
} }

View File

@ -26,19 +26,45 @@ locals {
module "organization" { module "organization" {
source = "../../../modules/organization" source = "../../../modules/organization"
organization_id = "organizations/${var.organization.id}" organization_id = "organizations/${var.organization.id}"
iam_additive = merge( iam_bindings_additive = merge(
{ {
"roles/resourcemanager.organizationViewer" = [ admins_org_viewer = {
"group:${local.groups.gcp-admins}" member = "group:${local.groups.gcp-admins}"
] role = "roles/resourcemanager.organizationViewer"
}
admins_org_policy_admin = {
member = "group:${local.groups.gcp-admins}"
role = "roles/orgpolicy.policyAdmin"
condition = {
title = "org_policy_tag_${var.tenant_config.short_name}_scoped_admins"
description = "Org policy tag scoped grant for tenant ${var.tenant_config.short_name}."
expression = local.iam_tenant_condition
}
}
sa_resman_org_policy_admin = {
member = module.automation-tf-resman-sa.iam_email
role = "roles/orgpolicy.policyAdmin"
condition = {
title = "org_policy_tag_${var.tenant_config.short_name}_scoped_sa_resman"
description = "Org policy tag scoped grant for tenant ${var.tenant_config.short_name}."
expression = local.iam_tenant_condition
}
}
}, },
local.billing_mode == "org" ? { local.billing_mode != "org" ? {} : {
"roles/billing.admin" = [ admins_billing_admin = {
"group:${local.groups.gcp-admins}", member = "group:${local.groups.gcp-admins}"
module.automation-tf-resman-sa.iam_email role = "roles/billing.admin"
] }
"roles/billing.costsManager" = ["group:${local.groups.gcp-admins}"] admins_billing_costs_manager = {
} : {} member = "group:${local.groups.gcp-admins}"
role = "roles/billing.costsManager"
}
sa_resman_billing_admin = {
member = module.automation-tf-resman-sa.iam_email
role = "roles/billing.admin"
}
}
) )
tags = { tags = {
tenant = { tenant = {
@ -50,6 +76,8 @@ module "organization" {
} }
} }
# TODO: use tag IAM with id in the organization module
resource "google_tags_tag_value_iam_member" "resman_tag_user" { resource "google_tags_tag_value_iam_member" "resman_tag_user" {
for_each = var.tag_values for_each = var.tag_values
tag_value = each.value tag_value = each.value
@ -64,21 +92,4 @@ resource "google_tags_tag_value_iam_member" "admins_tag_viewer" {
member = "group:${local.groups.gcp-admins}" member = "group:${local.groups.gcp-admins}"
} }
# assign org policy admin with a tag-based condition to admin group and stage 1 SA
resource "google_organization_iam_member" "org_policy_admin_stage0" {
for_each = toset([
"group:${local.groups.gcp-admins}",
module.automation-tf-resman-sa.iam_email
])
org_id = var.organization.id
role = "roles/orgpolicy.policyAdmin"
member = each.key
condition {
title = "org_policy_tag_${var.tenant_config.short_name}_scoped"
description = "Org policy tag scoped grant for tenant ${var.tenant_config.short_name}."
expression = local.iam_tenant_condition
}
}
# tag-based condition for service accounts is in the automation-sa file # tag-based condition for service accounts is in the automation-sa file

View File

@ -20,8 +20,9 @@ locals {
"${path.module}/templates/workflow-${v.type}.yaml", ( "${path.module}/templates/workflow-${v.type}.yaml", (
k == "bootstrap" k == "bootstrap"
? { ? {
audiences = local.cicd_providers[v["identity_provider"]].audiences
identity_provider = try( identity_provider = try(
local.cicd_identity_providers[v["identity_provider"]].name, "" local.cicd_providers[v["identity_provider"]].name, ""
) )
outputs_bucket = var.automation.outputs_bucket outputs_bucket = var.automation.outputs_bucket
service_account = try( service_account = try(
@ -36,8 +37,9 @@ locals {
] ]
} }
: { : {
audiences = local.cicd_providers[v["identity_provider"]].audiences
identity_provider = try( identity_provider = try(
local.cicd_identity_providers[v["identity_provider"]].name, "" local.cicd_providers[v["identity_provider"]].name, ""
) )
outputs_bucket = module.automation-tf-output-gcs.name outputs_bucket = module.automation-tf-output-gcs.name
service_account = try( service_account = try(
@ -70,7 +72,7 @@ locals {
try(google_iam_workload_identity_pool.default.0.name, null), try(google_iam_workload_identity_pool.default.0.name, null),
var.automation.federated_identity_pool, var.automation.federated_identity_pool,
]) ])
federated_identity_providers = local.cicd_identity_providers federated_identity_providers = local.cicd_providers
service_accounts = merge( service_accounts = merge(
{ resman = module.automation-tf-resman-sa.email }, { resman = module.automation-tf-resman-sa.email },
{ {
@ -116,7 +118,7 @@ output "federated_identity" {
pool = try( pool = try(
google_iam_workload_identity_pool.default.0.name, null google_iam_workload_identity_pool.default.0.name, null
) )
providers = local.cicd_identity_providers providers = local.cicd_providers
} }
} }

View File

@ -1,4 +1,4 @@
# Copyright 2022 Google LLC # Copyright 2023 Google LLC
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -13,8 +13,6 @@
# limitations under the License. # limitations under the License.
default: default:
before_script:
- echo "$${CI_JOB_JWT_V2}" > token.txt
image: image:
name: hashicorp/terraform name: hashicorp/terraform
entrypoint: entrypoint:
@ -42,13 +40,26 @@ cache:
key: gcp-auth key: gcp-auth
paths: paths:
- cicd-sa-credentials.json - cicd-sa-credentials.json
- .tf-setup - token.txt
%{~ if tf_providers_file != "" ~}
- ${tf_providers_file}
%{~ endif ~}
%{~ for f in tf_var_files ~}
- ${f}
%{~ endfor ~}
gcp-auth: gcp-auth:
id_tokens:
GITLAB_TOKEN:
aud:
%{~ for aud in audiences ~}
- ${aud}
%{~ endfor ~}
image: image:
name: google/cloud-sdk:slim name: google/cloud-sdk:slim
stage: gcp-auth stage: gcp-auth
script: script:
- echo "$${GITLAB_TOKEN}" > token.txt
- | - |
gcloud iam workload-identity-pools create-cred-config \ gcloud iam workload-identity-pools create-cred-config \
$${FAST_WIF_PROVIDER} \ $${FAST_WIF_PROVIDER} \
@ -56,6 +67,7 @@ gcp-auth:
--service-account-token-lifetime-seconds=3600 \ --service-account-token-lifetime-seconds=3600 \
--output-file=$${GOOGLE_CREDENTIALS} \ --output-file=$${GOOGLE_CREDENTIALS} \
--credential-source-file=token.txt --credential-source-file=token.txt
tf-files: tf-files:
dependencies: dependencies:
- gcp-auth - gcp-auth
@ -65,17 +77,18 @@ tf-files:
script: script:
# - gcloud components install -q alpha # - gcloud components install -q alpha
- gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS} - gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS}
- mkdir -p .tf-setup
%{~ if tf_providers_file != "" ~} %{~ if tf_providers_file != "" ~}
- | - gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/providers/${tf_providers_file}" ./
gcloud alpha storage cp -r \
"gs://$${FAST_OUTPUTS_BUCKET}/providers/$${TF_PROVIDERS_FILE}" .tf-setup/
%{~ endif ~} %{~ endif ~}
- | %{~ for f in tf_var_files ~}
gcloud alpha storage cp -r \ - gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/tfvars/${f}" ./
"gs://$${FAST_OUTPUTS_BUCKET}/tfvars" .tf-setup/ %{~ endfor ~}
- ls -l
tf-plan: tf-plan:
dependencies:
- tf-files
stage: tf-plan
# uncomment the following lines and set the SSH key secret for private modules repo # uncomment the following lines and set the SSH key secret for private modules repo
# before_script: # before_script:
# - | # - |
@ -84,20 +97,15 @@ tf-plan:
# mkdir -p ~/.ssh # mkdir -p ~/.ssh
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
stage: tf-plan
script: script:
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
- |
for f in $${TF_VAR_FILES}; do
ln -s ".tf-setup/tfvars/$f" ./
done
- terraform init - terraform init
- terraform validate - terraform validate
- terraform plan - terraform plan
dependencies:
- tf-files
tf-apply: tf-apply:
dependencies:
- tf-files
stage: tf-apply
# uncomment the following lines and set the SSH key secret for private modules repo # uncomment the following lines and set the SSH key secret for private modules repo
# before_script: # before_script:
# - | # - |
@ -106,18 +114,10 @@ tf-apply:
# mkdir -p ~/.ssh # mkdir -p ~/.ssh
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
stage: tf-apply
script: script:
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
- |
for f in $${TF_VAR_FILES}; do
ln -s ".tf-setup/tfvars/$f" ./
done
- terraform init - terraform init
- terraform validate - terraform validate
- terraform apply -input=false -auto-approve - terraform apply -input=false -auto-approve
dependencies:
- tf-files
when: manual when: manual
only: only:
variables: variables:

View File

@ -26,6 +26,7 @@ variable "automation" {
project_number = string project_number = string
federated_identity_pool = string federated_identity_pool = string
federated_identity_providers = map(object({ federated_identity_providers = map(object({
audiences = list(string)
issuer = string issuer = string
issuer_uri = string issuer_uri = string
name = string name = string
@ -118,12 +119,12 @@ variable "fast_features" {
variable "federated_identity_providers" { variable "federated_identity_providers" {
description = "Workload Identity Federation pools. The `cicd_repositories` variable references keys here." description = "Workload Identity Federation pools. The `cicd_repositories` variable references keys here."
type = map(object({ type = map(object({
attribute_condition = string attribute_condition = optional(string)
issuer = string issuer = string
custom_settings = object({ custom_settings = optional(object({
issuer_uri = string issuer_uri = optional(string)
allowed_audiences = list(string) audiences = optional(list(string), [])
}) }), {})
})) }))
default = {} default = {}
nullable = false nullable = false
@ -135,16 +136,38 @@ variable "group_iam" {
default = {} default = {}
} }
variable "groups" {
# tfdoc:variable:source 0-bootstrap
# https://cloud.google.com/docs/enterprise/setup-checklist
description = "Group names or emails to grant organization-level permissions. If just the name is provided, the default organization domain is assumed."
type = object({
gcp-devops = optional(string)
gcp-network-admins = optional(string)
gcp-security-admins = optional(string)
})
default = {}
nullable = false
}
variable "iam" { variable "iam" {
description = "Tenant-level custom IAM settings in role => [principal] format." description = "Tenant-level custom IAM settings in role => [principal] format."
type = map(list(string)) type = map(list(string))
default = {} default = {}
} }
variable "iam_additive" { variable "iam_bindings_additive" {
description = "Tenant-level custom IAM settings in role => [principal] format for non-authoritative bindings." description = "Individual additive IAM bindings. Keys are arbitrary."
type = map(list(string)) type = map(object({
default = {} member = string
role = string
condition = optional(object({
expression = string
title = string
description = optional(string)
}))
}))
nullable = false
default = {}
} }
variable "locations" { variable "locations" {

View File

@ -126,7 +126,6 @@ Once the configuration is done just go through the usual `init/apply` cycle. On
<!-- TFDOC OPTS files:1 show_extra:1 --> <!-- TFDOC OPTS files:1 show_extra:1 -->
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Files ## Files
| name | description | modules | resources | | name | description | modules | resources |
@ -154,21 +153,21 @@ Once the configuration is done just go through the usual `init/apply` cycle. On
| name | description | type | required | default | producer | | name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|:---:|
| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | <code title="object&#40;&#123;&#10; outputs_bucket &#61; string&#10; project_id &#61; string&#10; project_number &#61; string&#10; federated_identity_pools &#61; list&#40;string&#41;&#10; federated_identity_providers &#61; map&#40;object&#40;&#123;&#10; issuer &#61; string&#10; issuer_uri &#61; string&#10; name &#61; string&#10; principal_tpl &#61; string&#10; principalset_tpl &#61; string&#10; &#125;&#41;&#41;&#10; service_accounts &#61; object&#40;&#123;&#10; networking &#61; string&#10; resman &#61; string&#10; security &#61; string&#10; dp-dev &#61; optional&#40;string&#41;&#10; dp-prod &#61; optional&#40;string&#41;&#10; gke-dev &#61; optional&#40;string&#41;&#10; gke-prod &#61; optional&#40;string&#41;&#10; pf-dev &#61; optional&#40;string&#41;&#10; pf-prod &#61; optional&#40;string&#41;&#10; sandbox &#61; optional&#40;string&#41;&#10; teams &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> | | [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | <code title="object&#40;&#123;&#10; outputs_bucket &#61; string&#10; project_id &#61; string&#10; project_number &#61; string&#10; federated_identity_pools &#61; list&#40;string&#41;&#10; federated_identity_providers &#61; map&#40;object&#40;&#123;&#10; audiences &#61; list&#40;string&#41;&#10; issuer &#61; string&#10; issuer_uri &#61; string&#10; name &#61; string&#10; principal_tpl &#61; string&#10; principalset_tpl &#61; string&#10; &#125;&#41;&#41;&#10; service_accounts &#61; object&#40;&#123;&#10; networking &#61; string&#10; resman &#61; string&#10; security &#61; string&#10; dp-dev &#61; optional&#40;string&#41;&#10; dp-prod &#61; optional&#40;string&#41;&#10; gke-dev &#61; optional&#40;string&#41;&#10; gke-prod &#61; optional&#40;string&#41;&#10; pf-dev &#61; optional&#40;string&#41;&#10; pf-prod &#61; optional&#40;string&#41;&#10; sandbox &#61; optional&#40;string&#41;&#10; teams &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> |
| [billing_account](variables.tf#L51) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | <code title="object&#40;&#123;&#10; id &#61; string&#10; is_org_level &#61; optional&#40;bool, true&#41;&#10; no_iam &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> | | [billing_account](variables.tf#L52) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | <code title="object&#40;&#123;&#10; id &#61; string&#10; is_org_level &#61; optional&#40;bool, true&#41;&#10; no_iam &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> |
| [organization](variables.tf#L204) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; number&#10; customer_id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> | | [organization](variables.tf#L205) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; number&#10; customer_id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> |
| [prefix](variables.tf#L226) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>0-bootstrap</code> | | [prefix](variables.tf#L227) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>0-bootstrap</code> |
| [root_node](variables.tf#L237) | Root folder node for the tenant, in folders/nnnnnn format. | <code>string</code> | ✓ | | | | [root_node](variables.tf#L237) | Root folder node for the tenant, in folders/nnnnnn format. | <code>string</code> | ✓ | | |
| [short_name](variables.tf#L242) | Short name used to identify the tenant. | <code>string</code> | ✓ | | | | [short_name](variables.tf#L242) | Short name used to identify the tenant. | <code>string</code> | ✓ | | |
| [tags](variables.tf#L247) | Resource management tags. | <code title="object&#40;&#123;&#10; keys &#61; object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10; tenant &#61; string&#10; &#125;&#41;&#10; names &#61; object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10; tenant &#61; string&#10; &#125;&#41;&#10; values &#61; map&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | | [tags](variables.tf#L247) | Resource management tags. | <code title="object&#40;&#123;&#10; keys &#61; object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10; tenant &#61; string&#10; &#125;&#41;&#10; names &#61; object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10; tenant &#61; string&#10; &#125;&#41;&#10; values &#61; map&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | |
| [cicd_repositories](variables.tf#L62) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | <code title="object&#40;&#123;&#10; data_platform_dev &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; data_platform_prod &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; gke_dev &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; gke_prod &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; networking &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; project_factory_dev &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; project_factory_prod &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; security &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | | [cicd_repositories](variables.tf#L63) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | <code title="object&#40;&#123;&#10; data_platform_dev &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; data_platform_prod &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; gke_dev &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; gke_prod &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; networking &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; project_factory_dev &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; project_factory_prod &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; security &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | |
| [custom_roles](variables.tf#L144) | Custom roles defined at the org level, in key => id format. | <code title="object&#40;&#123;&#10; service_project_network_admin &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | <code>0-bootstrap</code> | | [custom_roles](variables.tf#L145) | Custom roles defined at the org level, in key => id format. | <code title="object&#40;&#123;&#10; service_project_network_admin &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | <code>0-bootstrap</code> |
| [data_dir](variables.tf#L153) | Relative path for the folder storing configuration data. | <code>string</code> | | <code>&#34;data&#34;</code> | | | [data_dir](variables.tf#L154) | Relative path for the folder storing configuration data. | <code>string</code> | | <code>&#34;data&#34;</code> | |
| [fast_features](variables.tf#L159) | Selective control for top-level FAST features. | <code title="object&#40;&#123;&#10; data_platform &#61; optional&#40;bool, false&#41;&#10; gke &#61; optional&#40;bool, false&#41;&#10; project_factory &#61; optional&#40;bool, false&#41;&#10; sandbox &#61; optional&#40;bool, false&#41;&#10; teams &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | <code>0-0-bootstrap</code> | | [fast_features](variables.tf#L160) | Selective control for top-level FAST features. | <code title="object&#40;&#123;&#10; data_platform &#61; optional&#40;bool, false&#41;&#10; gke &#61; optional&#40;bool, false&#41;&#10; project_factory &#61; optional&#40;bool, false&#41;&#10; sandbox &#61; optional&#40;bool, false&#41;&#10; teams &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | <code>0-0-bootstrap</code> |
| [groups](variables.tf#L173) | Group names to grant organization-level permissions. | <code title="object&#40;&#123;&#10; gcp-devops &#61; optional&#40;string&#41;&#10; gcp-network-admins &#61; optional&#40;string&#41;&#10; gcp-security-admins &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | <code>0-bootstrap</code> | | [groups](variables.tf#L174) | Group names to grant organization-level permissions. | <code title="object&#40;&#123;&#10; gcp-devops &#61; optional&#40;string&#41;&#10; gcp-network-admins &#61; optional&#40;string&#41;&#10; gcp-security-admins &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | <code>0-bootstrap</code> |
| [locations](variables.tf#L186) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object&#40;&#123;&#10; bq &#61; string&#10; gcs &#61; string&#10; logging &#61; string&#10; pubsub &#61; list&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; bq &#61; &#34;EU&#34;&#10; gcs &#61; &#34;EU&#34;&#10; logging &#61; &#34;global&#34;&#10; pubsub &#61; &#91;&#93;&#10;&#125;">&#123;&#8230;&#125;</code> | <code>0-bootstrap</code> | | [locations](variables.tf#L187) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object&#40;&#123;&#10; bq &#61; string&#10; gcs &#61; string&#10; logging &#61; string&#10; pubsub &#61; list&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; bq &#61; &#34;EU&#34;&#10; gcs &#61; &#34;EU&#34;&#10; logging &#61; &#34;global&#34;&#10; pubsub &#61; &#91;&#93;&#10;&#125;">&#123;&#8230;&#125;</code> | <code>0-bootstrap</code> |
| [organization_policy_data_path](variables.tf#L214) | Path for the data folder used by the organization policies factory. | <code>string</code> | | <code>null</code> | | | [organization_policy_data_path](variables.tf#L215) | Path for the data folder used by the organization policies factory. | <code>string</code> | | <code>null</code> | |
| [outputs_location](variables.tf#L220) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | | | [outputs_location](variables.tf#L221) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | |
| [team_folders](variables.tf#L265) | Team folders to be created. Format is described in a code comment. | <code title="map&#40;object&#40;&#123;&#10; descriptive_name &#61; string&#10; group_iam &#61; map&#40;list&#40;string&#41;&#41;&#10; impersonation_groups &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> | | | [team_folders](variables.tf#L265) | Team folders to be created. Format is described in a code comment. | <code title="map&#40;object&#40;&#123;&#10; descriptive_name &#61; string&#10; group_iam &#61; map&#40;list&#40;string&#41;&#41;&#10; impersonation_groups &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> | |
| [test_skip_data_sources](variables.tf#L275) | Used when testing to bypass data sources. | <code>bool</code> | | <code>false</code> | | | [test_skip_data_sources](variables.tf#L275) | Used when testing to bypass data sources. | <code>bool</code> | | <code>false</code> | |
@ -176,15 +175,14 @@ Once the configuration is done just go through the usual `init/apply` cycle. On
| name | description | sensitive | consumers | | name | description | sensitive | consumers |
|---|---|:---:|---| |---|---|:---:|---|
| [cicd_repositories](outputs.tf#L189) | WIF configuration for CI/CD repositories. | | | | [cicd_repositories](outputs.tf#L192) | WIF configuration for CI/CD repositories. | | |
| [dataplatform](outputs.tf#L203) | Data for the Data Platform stage. | | | | [dataplatform](outputs.tf#L206) | Data for the Data Platform stage. | | |
| [gke_multitenant](outputs.tf#L219) | Data for the GKE multitenant stage. | | <code>03-gke-multitenant</code> | | [gke_multitenant](outputs.tf#L222) | Data for the GKE multitenant stage. | | <code>03-gke-multitenant</code> |
| [networking](outputs.tf#L240) | Data for the networking stage. | | | | [networking](outputs.tf#L243) | Data for the networking stage. | | |
| [project_factories](outputs.tf#L249) | Data for the project factories stage. | | | | [project_factories](outputs.tf#L252) | Data for the project factories stage. | | |
| [providers](outputs.tf#L264) | Terraform provider files for this stage and dependent stages. | ✓ | <code>02-networking</code> · <code>02-security</code> · <code>03-dataplatform</code> · <code>xx-sandbox</code> · <code>xx-teams</code> | | [providers](outputs.tf#L267) | Terraform provider files for this stage and dependent stages. | ✓ | <code>02-networking</code> · <code>02-security</code> · <code>03-dataplatform</code> · <code>xx-sandbox</code> · <code>xx-teams</code> |
| [sandbox](outputs.tf#L271) | Data for the sandbox stage. | | <code>xx-sandbox</code> | | [sandbox](outputs.tf#L274) | Data for the sandbox stage. | | <code>xx-sandbox</code> |
| [security](outputs.tf#L285) | Data for the networking stage. | | <code>02-security</code> | | [security](outputs.tf#L288) | Data for the networking stage. | | <code>02-security</code> |
| [teams](outputs.tf#L295) | Data for the teams stage. | | | | [teams](outputs.tf#L298) | Data for the teams stage. | | |
| [tfvars](outputs.tf#L307) | Terraform variable files for the following stages. | ✓ | | | [tfvars](outputs.tf#L310) | Terraform variable files for the following stages. | ✓ | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -62,6 +62,9 @@ locals {
for k, v in local.cicd_repositories : k => templatefile( for k, v in local.cicd_repositories : k => templatefile(
"${path.module}/templates/workflow-${v.type}.yaml", "${path.module}/templates/workflow-${v.type}.yaml",
merge(local.cicd_workflow_attrs[k], { merge(local.cicd_workflow_attrs[k], {
audiences = try(
local.cicd_identity_providers[v.identity_provider].audiences, null
)
identity_provider = try( identity_provider = try(
local.cicd_identity_providers[v.identity_provider].name, null local.cicd_identity_providers[v.identity_provider].name, null
) )

View File

@ -26,16 +26,19 @@ module "root-folder" {
) )
name = var.test_skip_data_sources ? "Test" : null name = var.test_skip_data_sources ? "Test" : null
# end test attributes # end test attributes
iam_additive = { iam_bindings_additive = {
"roles/accesscontextmanager.policyAdmin" = [ sa_net_fw_policy_admin = {
local.automation_sas_iam.security member = local.automation_sas_iam.networking
] role = "roles/compute.orgFirewallPolicyAdmin"
"roles/compute.orgFirewallPolicyAdmin" = [ }
local.automation_sas_iam.networking sa_net_xpn_admin = {
] member = local.automation_sas_iam.networking
"roles/compute.xpnAdmin" = [ role = "roles/compute.xpnAdmin"
local.automation_sas_iam.networking }
] sa_sec_vpcsc_admin = {
member = local.automation_sas_iam.security
role = "roles/accesscontextmanager.policyAdmin"
}
} }
org_policies_data_path = var.organization_policy_data_path org_policies_data_path = var.organization_policy_data_path
} }

View File

@ -1,4 +1,4 @@
# Copyright 2022 Google LLC # Copyright 2023 Google LLC
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -13,8 +13,6 @@
# limitations under the License. # limitations under the License.
default: default:
before_script:
- echo "$${CI_JOB_JWT_V2}" > token.txt
image: image:
name: hashicorp/terraform name: hashicorp/terraform
entrypoint: entrypoint:
@ -27,7 +25,9 @@ variables:
FAST_SERVICE_ACCOUNT: ${service_account} FAST_SERVICE_ACCOUNT: ${service_account}
FAST_WIF_PROVIDER: ${identity_provider} FAST_WIF_PROVIDER: ${identity_provider}
SSH_AUTH_SOCK: /tmp/ssh_agent.sock SSH_AUTH_SOCK: /tmp/ssh_agent.sock
%{~ if tf_providers_file != "" ~}
TF_PROVIDERS_FILE: ${tf_providers_file} TF_PROVIDERS_FILE: ${tf_providers_file}
%{~ endif ~}
TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)}
stages: stages:
@ -40,13 +40,26 @@ cache:
key: gcp-auth key: gcp-auth
paths: paths:
- cicd-sa-credentials.json - cicd-sa-credentials.json
- .tf-setup - token.txt
%{~ if tf_providers_file != "" ~}
- ${tf_providers_file}
%{~ endif ~}
%{~ for f in tf_var_files ~}
- ${f}
%{~ endfor ~}
gcp-auth: gcp-auth:
id_tokens:
GITLAB_TOKEN:
aud:
%{~ for aud in audiences ~}
- ${aud}
%{~ endfor ~}
image: image:
name: google/cloud-sdk:slim name: google/cloud-sdk:slim
stage: gcp-auth stage: gcp-auth
script: script:
- echo "$${GITLAB_TOKEN}" > token.txt
- | - |
gcloud iam workload-identity-pools create-cred-config \ gcloud iam workload-identity-pools create-cred-config \
$${FAST_WIF_PROVIDER} \ $${FAST_WIF_PROVIDER} \
@ -54,6 +67,7 @@ gcp-auth:
--service-account-token-lifetime-seconds=3600 \ --service-account-token-lifetime-seconds=3600 \
--output-file=$${GOOGLE_CREDENTIALS} \ --output-file=$${GOOGLE_CREDENTIALS} \
--credential-source-file=token.txt --credential-source-file=token.txt
tf-files: tf-files:
dependencies: dependencies:
- gcp-auth - gcp-auth
@ -63,15 +77,18 @@ tf-files:
script: script:
# - gcloud components install -q alpha # - gcloud components install -q alpha
- gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS} - gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS}
- mkdir -p .tf-setup %{~ if tf_providers_file != "" ~}
- | - gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/providers/${tf_providers_file}" ./
gcloud alpha storage cp -r \ %{~ endif ~}
"gs://$${FAST_OUTPUTS_BUCKET}/providers/$${TF_PROVIDERS_FILE}" .tf-setup/ %{~ for f in tf_var_files ~}
- | - gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/tfvars/${f}" ./
gcloud alpha storage cp -r \ %{~ endfor ~}
"gs://$${FAST_OUTPUTS_BUCKET}/tfvars" .tf-setup/ - ls -l
tf-plan: tf-plan:
dependencies:
- tf-files
stage: tf-plan
# uncomment the following lines and set the SSH key secret for private modules repo # uncomment the following lines and set the SSH key secret for private modules repo
# before_script: # before_script:
# - | # - |
@ -80,20 +97,15 @@ tf-plan:
# mkdir -p ~/.ssh # mkdir -p ~/.ssh
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
stage: tf-plan
script: script:
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
- |
for f in $${TF_VAR_FILES}; do
ln -s ".tf-setup/tfvars/$f" ./
done
- terraform init - terraform init
- terraform validate - terraform validate
- terraform plan - terraform plan
dependencies:
- tf-files
tf-apply: tf-apply:
dependencies:
- tf-files
stage: tf-apply
# uncomment the following lines and set the SSH key secret for private modules repo # uncomment the following lines and set the SSH key secret for private modules repo
# before_script: # before_script:
# - | # - |
@ -102,18 +114,10 @@ tf-apply:
# mkdir -p ~/.ssh # mkdir -p ~/.ssh
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
stage: tf-apply
script: script:
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
- |
for f in $${TF_VAR_FILES}; do
ln -s ".tf-setup/tfvars/$f" ./
done
- terraform init - terraform init
- terraform validate - terraform validate
- terraform apply -input=false -auto-approve - terraform apply -input=false -auto-approve
dependencies:
- tf-files
when: manual when: manual
only: only:
variables: variables:

View File

@ -26,6 +26,7 @@ variable "automation" {
project_number = string project_number = string
federated_identity_pools = list(string) federated_identity_pools = list(string)
federated_identity_providers = map(object({ federated_identity_providers = map(object({
audiences = list(string)
issuer = string issuer = string
issuer_uri = string issuer_uri = string
name = string name = string
@ -227,7 +228,6 @@ variable "prefix" {
# tfdoc:variable:source 0-bootstrap # tfdoc:variable:source 0-bootstrap
description = "Prefix used for resources that need unique names. Use 9 characters or less." description = "Prefix used for resources that need unique names. Use 9 characters or less."
type = string type = string
validation { validation {
condition = try(length(var.prefix), 0) < 13 condition = try(length(var.prefix), 0) < 13
error_message = "Use a maximum of 12 characters for prefix (which is a combination of org prefix and tenant short name)." error_message = "Use a maximum of 12 characters for prefix (which is a combination of org prefix and tenant short name)."

View File

@ -209,10 +209,10 @@ Then make sure you have configured the correct values for the following variable
- `organization.id`, `organization.domain`, `organization.customer_id` - `organization.id`, `organization.domain`, `organization.customer_id`
the id, domain and customer id of your organization, derived from the Cloud Console UI or by running `gcloud organizations list` the id, domain and customer id of your organization, derived from the Cloud Console UI or by running `gcloud organizations list`
- `prefix` - `prefix`
the fixed org-level prefix used in your naming, maximum 9 characters long. Note that if you are using multitenant stages, then you will later need to configure a `tenant prefix`. the fixed org-level prefix used in your naming, maximum 9 characters long. Note that if you are using multitenant stages, then you will later need to configure a `tenant prefix`.
This `tenant prefix` can have a maximum length of 2 characters, This `tenant prefix` can have a maximum length of 2 characters,
plus any unused characters from the from the `prefix`. plus any unused characters from the from the `prefix`.
For example, if you specify a `prefix` that is 7 characters long, For example, if you specify a `prefix` that is 7 characters long,
then your `tenant prefix` can have a maximum of 4 characters. then your `tenant prefix` can have a maximum of 4 characters.
You can also adapt the example that follows to your needs: You can also adapt the example that follows to your needs:
@ -357,11 +357,20 @@ In code, the distinction above reflects on how IAM bindings are specified in the
This makes it easy to tweak user roles by adding mappings to the `iam_groups` variables of the relevant resources, without having to understand and deal with the details of service account roles. This makes it easy to tweak user roles by adding mappings to the `iam_groups` variables of the relevant resources, without having to understand and deal with the details of service account roles.
In those cases where roles need to be assigned to end-user service accounts (e.g. an application or pipeline service account), we offer a stage-level `iam` variable that allows pinpointing individual role/members pairs, without having to touch the code internals, to avoid the risk of breaking a critical role for a robot account. The variable can also be used to assign roles to specific users or to groups external to the organization, e.g. to support external suppliers. One more critical difference in IAM bindings is between authoritative and additive:
The one exception to this convention is for roles which are part of the delegated grant condition described above, and which can then be assigned from other stages. In this case, use the `iam_additive` variable as they are implemented with non-authoritative resources. Using non-authoritative bindings ensure that re-executing this stage will not override any bindings set in downstream stages. - authoritative bindings have complete control on principals for a given role; this is the recommended best practice when a single automation actor controls the role, as it removes drift each time Terraform runs
- additive bindings have control only on given role/principal pairs, and need to be used whenever multiple automation actors need to control the role, as is the case for the network user role in Shared VPC setups, and many other situations
A full reference of IAM roles managed by this stage [is available here](./IAM.md). This stage groups all IAM definitions in the [organization-iam.tf](./organization-iam.tf) file, to allow easy parsing of roles assigned to each group and machine identity.
When customizations are needed, three stage-level variables allow injecting additional bindings to match the desired setup:
- `group_iam` allows adding authoritative bindings for groups
- `iam` allows adding authoritative bindings for any type of supported principal, and is merged with the internal `iam` local and then with group bindings at the module level
- `iam_bindings_additive` allows adding individual role/member pairs, and also supports IAM conditions
Refer to the [project module](../../../modules/project/) for examples on how to use the IAM variables, and they are an interface shared across all our modules.
### Log sinks and log destinations ### Log sinks and log destinations
@ -421,7 +430,7 @@ federated_identity_providers = {
attribute_condition = "attribute.namespace_path==\"my-gitlab-org\"" attribute_condition = "attribute.namespace_path==\"my-gitlab-org\""
issuer = "gitlab" issuer = "gitlab"
custom_settings = { custom_settings = {
allowed_audiences = ["https://gitlab.fast.example.com"] audiences = ["https://gitlab.fast.example.com"]
issuer_uri = "https://gitlab.fast.example.com" issuer_uri = "https://gitlab.fast.example.com"
} }
} }
@ -498,7 +507,8 @@ The remaining configuration is manual, as it regards the repositories themselves
| [identity-providers.tf](./identity-providers.tf) | Workload Identity Federation provider definitions. | | <code>google_iam_workload_identity_pool</code> · <code>google_iam_workload_identity_pool_provider</code> | | [identity-providers.tf](./identity-providers.tf) | Workload Identity Federation provider definitions. | | <code>google_iam_workload_identity_pool</code> · <code>google_iam_workload_identity_pool_provider</code> |
| [log-export.tf](./log-export.tf) | Audit log project and sink. | <code>bigquery-dataset</code> · <code>gcs</code> · <code>logging-bucket</code> · <code>project</code> · <code>pubsub</code> | | | [log-export.tf](./log-export.tf) | Audit log project and sink. | <code>bigquery-dataset</code> · <code>gcs</code> · <code>logging-bucket</code> · <code>project</code> · <code>pubsub</code> | |
| [main.tf](./main.tf) | Module-level locals and resources. | | | | [main.tf](./main.tf) | Module-level locals and resources. | | |
| [organization.tf](./organization.tf) | Organization-level IAM. | <code>organization</code> | <code>google_organization_iam_binding</code> | | [organization-iam.tf](./organization-iam.tf) | Organization-level IAM bindings locals. | | |
| [organization.tf](./organization.tf) | Organization-level IAM. | <code>organization</code> | |
| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | <code>local_file</code> | | [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | <code>local_file</code> |
| [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | <code>google_storage_bucket_object</code> | | [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | <code>google_storage_bucket_object</code> |
| [outputs.tf](./outputs.tf) | Module outputs. | | | | [outputs.tf](./outputs.tf) | Module outputs. | | |
@ -509,34 +519,35 @@ The remaining configuration is manual, as it regards the repositories themselves
| name | description | type | required | default | producer | | name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|:---:|
| [billing_account](variables.tf#L17) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | <code title="object&#40;&#123;&#10; id &#61; string&#10; is_org_level &#61; optional&#40;bool, true&#41;&#10; no_iam &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | | [billing_account](variables.tf#L17) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | <code title="object&#40;&#123;&#10; id &#61; string&#10; is_org_level &#61; optional&#40;bool, true&#41;&#10; no_iam &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | |
| [organization](variables.tf#L206) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; number&#10; customer_id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | | [organization](variables.tf#L219) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; number&#10; customer_id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | |
| [prefix](variables.tf#L221) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | | | [prefix](variables.tf#L234) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | |
| [bootstrap_user](variables.tf#L27) | Email of the nominal user running this stage for the first time. | <code>string</code> | | <code>null</code> | | | [bootstrap_user](variables.tf#L27) | Email of the nominal user running this stage for the first time. | <code>string</code> | | <code>null</code> | |
| [cicd_repositories](variables.tf#L33) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | <code title="object&#40;&#123;&#10; bootstrap &#61; optional&#40;object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#41;&#10; resman &#61; optional&#40;object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | | [cicd_repositories](variables.tf#L33) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | <code title="object&#40;&#123;&#10; bootstrap &#61; optional&#40;object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#41;&#10; resman &#61; optional&#40;object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | |
| [custom_role_names](variables.tf#L79) | Names of custom roles defined at the org level. | <code title="object&#40;&#123;&#10; organization_iam_admin &#61; string&#10; service_project_network_admin &#61; string&#10; tenant_network_admin &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; organization_iam_admin &#61; &#34;organizationIamAdmin&#34;&#10; service_project_network_admin &#61; &#34;serviceProjectNetworkAdmin&#34;&#10; tenant_network_admin &#61; &#34;tenantNetworkAdmin&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | | | [custom_role_names](variables.tf#L79) | Names of custom roles defined at the org level. | <code title="object&#40;&#123;&#10; organization_iam_admin &#61; string&#10; service_project_network_admin &#61; string&#10; tenant_network_admin &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; organization_iam_admin &#61; &#34;organizationIamAdmin&#34;&#10; service_project_network_admin &#61; &#34;serviceProjectNetworkAdmin&#34;&#10; tenant_network_admin &#61; &#34;tenantNetworkAdmin&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | |
| [custom_roles](variables.tf#L93) | Map of role names => list of permissions to additionally create at the organization level. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | | [custom_roles](variables.tf#L93) | Map of role names => list of permissions to additionally create at the organization level. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [fast_features](variables.tf#L100) | Selective control for top-level FAST features. | <code title="object&#40;&#123;&#10; data_platform &#61; optional&#40;bool, false&#41;&#10; gke &#61; optional&#40;bool, false&#41;&#10; project_factory &#61; optional&#40;bool, false&#41;&#10; sandbox &#61; optional&#40;bool, false&#41;&#10; teams &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | | | [fast_features](variables.tf#L100) | Selective control for top-level FAST features. | <code title="object&#40;&#123;&#10; data_platform &#61; optional&#40;bool, false&#41;&#10; gke &#61; optional&#40;bool, false&#41;&#10; project_factory &#61; optional&#40;bool, false&#41;&#10; sandbox &#61; optional&#40;bool, false&#41;&#10; teams &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | |
| [federated_identity_providers](variables.tf#L113) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | <code title="map&#40;object&#40;&#123;&#10; attribute_condition &#61; optional&#40;string&#41;&#10; issuer &#61; string&#10; custom_settings &#61; optional&#40;object&#40;&#123;&#10; issuer_uri &#61; optional&#40;string&#41;&#10; allowed_audiences &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | | [federated_identity_providers](variables.tf#L113) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | <code title="map&#40;object&#40;&#123;&#10; attribute_condition &#61; optional&#40;string&#41;&#10; issuer &#61; string&#10; custom_settings &#61; optional&#40;object&#40;&#123;&#10; issuer_uri &#61; optional&#40;string&#41;&#10; audiences &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [groups](variables.tf#L132) | Group names or emails to grant organization-level permissions. If just the name is provided, the default organization domain is assumed. | <code>map&#40;string&#41;</code> | | <code title="&#123;&#10; gcp-billing-admins &#61; &#34;gcp-billing-admins&#34;,&#10; gcp-devops &#61; &#34;gcp-devops&#34;,&#10; gcp-network-admins &#61; &#34;gcp-network-admins&#34;&#10; gcp-organization-admins &#61; &#34;gcp-organization-admins&#34;&#10; gcp-security-admins &#61; &#34;gcp-security-admins&#34;&#10; gcp-support &#61; &#34;gcp-devops&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | | | [group_iam](variables.tf#L132) | Organization-level authoritative IAM binding for groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [iam](variables.tf#L150) | Organization-level custom IAM settings in role => [principal] format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | | [groups](variables.tf#L140) | Group names or emails to grant organization-level permissions. If just the name is provided, the default organization domain is assumed. | <code>map&#40;string&#41;</code> | | <code title="&#123;&#10; gcp-billing-admins &#61; &#34;gcp-billing-admins&#34;,&#10; gcp-devops &#61; &#34;gcp-devops&#34;,&#10; gcp-network-admins &#61; &#34;gcp-network-admins&#34;&#10; gcp-organization-admins &#61; &#34;gcp-organization-admins&#34;&#10; gcp-security-admins &#61; &#34;gcp-security-admins&#34;&#10; gcp-support &#61; &#34;gcp-devops&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | |
| [iam_additive](variables.tf#L156) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | | [iam](variables.tf#L158) | Organization-level custom IAM settings in role => [principal] format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [locations](variables.tf#L162) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object&#40;&#123;&#10; bq &#61; string&#10; gcs &#61; string&#10; logging &#61; string&#10; pubsub &#61; list&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; bq &#61; &#34;EU&#34;&#10; gcs &#61; &#34;EU&#34;&#10; logging &#61; &#34;global&#34;&#10; pubsub &#61; &#91;&#93;&#10;&#125;">&#123;&#8230;&#125;</code> | | | [iam_bindings_additive](variables.tf#L165) | Organization-level custom additive IAM bindings. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [log_sinks](variables.tf#L181) | Org-level log sinks, in name => {type, filter} format. | <code title="map&#40;object&#40;&#123;&#10; filter &#61; string&#10; type &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code title="&#123;&#10; audit-logs &#61; &#123;&#10; filter &#61; &#34;logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Factivity&#92;&#34; OR logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Fsystem_event&#92;&#34;&#34;&#10; type &#61; &#34;logging&#34;&#10; &#125;&#10; vpc-sc &#61; &#123;&#10; filter &#61; &#34;protoPayload.metadata.&#64;type&#61;&#92;&#34;type.googleapis.com&#47;google.cloud.audit.VpcServiceControlAuditMetadata&#92;&#34;&#34;&#10; type &#61; &#34;logging&#34;&#10; &#125;&#10;&#125;">&#123;&#8230;&#125;</code> | | | [locations](variables.tf#L180) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object&#40;&#123;&#10; bq &#61; optional&#40;string, &#34;EU&#34;&#41;&#10; gcs &#61; optional&#40;string, &#34;EU&#34;&#41;&#10; logging &#61; optional&#40;string, &#34;global&#34;&#41;&#10; pubsub &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | |
| [outputs_location](variables.tf#L215) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | | | [log_sinks](variables.tf#L194) | Org-level log sinks, in name => {type, filter} format. | <code title="map&#40;object&#40;&#123;&#10; filter &#61; string&#10; type &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code title="&#123;&#10; audit-logs &#61; &#123;&#10; filter &#61; &#34;logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Factivity&#92;&#34; OR logName:&#92;&#34;&#47;logs&#47;cloudaudit.googleapis.com&#37;2Fsystem_event&#92;&#34;&#34;&#10; type &#61; &#34;logging&#34;&#10; &#125;&#10; vpc-sc &#61; &#123;&#10; filter &#61; &#34;protoPayload.metadata.&#64;type&#61;&#92;&#34;type.googleapis.com&#47;google.cloud.audit.VpcServiceControlAuditMetadata&#92;&#34;&#34;&#10; type &#61; &#34;logging&#34;&#10; &#125;&#10;&#125;">&#123;&#8230;&#125;</code> | |
| [project_parent_ids](variables.tf#L230) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | <code title="object&#40;&#123;&#10; automation &#61; string&#10; billing &#61; string&#10; logging &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; automation &#61; null&#10; billing &#61; null&#10; logging &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> | | | [outputs_location](variables.tf#L228) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | |
| [project_parent_ids](variables.tf#L243) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | <code title="object&#40;&#123;&#10; automation &#61; string&#10; billing &#61; string&#10; logging &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; automation &#61; null&#10; billing &#61; null&#10; logging &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> | |
## Outputs ## Outputs
| name | description | sensitive | consumers | | name | description | sensitive | consumers |
|---|---|:---:|---| |---|---|:---:|---|
| [automation](outputs.tf#L100) | Automation resources. | | | | [automation](outputs.tf#L102) | Automation resources. | | |
| [billing_dataset](outputs.tf#L105) | BigQuery dataset prepared for billing export. | | | | [billing_dataset](outputs.tf#L107) | BigQuery dataset prepared for billing export. | | |
| [cicd_repositories](outputs.tf#L110) | CI/CD repository configurations. | | | | [cicd_repositories](outputs.tf#L112) | CI/CD repository configurations. | | |
| [custom_roles](outputs.tf#L122) | Organization-level custom roles. | | | | [custom_roles](outputs.tf#L124) | Organization-level custom roles. | | |
| [federated_identity](outputs.tf#L127) | Workload Identity Federation pool and providers. | | | | [federated_identity](outputs.tf#L129) | Workload Identity Federation pool and providers. | | |
| [outputs_bucket](outputs.tf#L137) | GCS bucket where generated output files are stored. | | | | [outputs_bucket](outputs.tf#L139) | GCS bucket where generated output files are stored. | | |
| [project_ids](outputs.tf#L142) | Projects created by this stage. | | | | [project_ids](outputs.tf#L144) | Projects created by this stage. | | |
| [providers](outputs.tf#L152) | Terraform provider files for this stage and dependent stages. | ✓ | <code>stage-01</code> | | [providers](outputs.tf#L154) | Terraform provider files for this stage and dependent stages. | ✓ | <code>stage-01</code> |
| [service_accounts](outputs.tf#L159) | Automation service accounts created by this stage. | | | | [service_accounts](outputs.tf#L161) | Automation service accounts created by this stage. | | |
| [tfvars](outputs.tf#L168) | Terraform variable files for the following stages. | ✓ | | | [tfvars](outputs.tf#L170) | Terraform variable files for the following stages. | ✓ | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -16,6 +16,10 @@
# tfdoc:file:description Automation project and resources. # tfdoc:file:description Automation project and resources.
locals {
cicd_resman_sa = try(module.automation-tf-cicd-sa["resman"].iam_email, "")
}
module "automation-project" { module "automation-project" {
source = "../../../modules/project" source = "../../../modules/project"
billing_account = var.billing_account.id billing_account = var.billing_account.id
@ -151,11 +155,14 @@ module "automation-tf-resman-sa" {
prefix = local.prefix prefix = local.prefix
# allow SA used by CI/CD workflow to impersonate this SA # allow SA used by CI/CD workflow to impersonate this SA
# we use additive IAM to allow tenant CI/CD SAs to impersonate it # we use additive IAM to allow tenant CI/CD SAs to impersonate it
iam_additive = { iam_bindings_additive = (
"roles/iam.serviceAccountTokenCreator" = compact([ local.cicd_resman_sa == "" ? {} : {
try(module.automation-tf-cicd-sa["resman"].iam_email, null) cicd_token_creator = {
]) member = local.cicd_resman_sa
} role = "roles/iam.serviceAccountTokenCreator"
}
}
)
iam_storage_roles = { iam_storage_roles = {
(module.automation-tf-output-gcs.name) = ["roles/storage.admin"] (module.automation-tf-output-gcs.name) = ["roles/storage.admin"]
} }

View File

@ -20,9 +20,9 @@ locals {
cicd_providers = { cicd_providers = {
for k, v in google_iam_workload_identity_pool_provider.default : for k, v in google_iam_workload_identity_pool_provider.default :
k => { k => {
audience = try( audiences = concat(
v.oidc[0].allowed_audiences[0], v.oidc[0].allowed_audiences,
"https://iam.googleapis.com/${v.name}" ["https://iam.googleapis.com/${v.name}"]
) )
issuer = local.identity_providers[k].issuer issuer = local.identity_providers[k].issuer
issuer_uri = try(v.oidc[0].issuer_uri, null) issuer_uri = try(v.oidc[0].issuer_uri, null)
@ -39,10 +39,15 @@ locals {
( (
try(v.type, null) == "sourcerepo" try(v.type, null) == "sourcerepo"
|| ||
contains(keys(local.identity_providers), coalesce(try(v.identity_provider, null), ":")) contains(
keys(local.identity_providers),
coalesce(try(v.identity_provider, null), ":")
)
) )
&& &&
fileexists(format("${path.module}/templates/workflow-%s.yaml", try(v.type, ""))) fileexists(
format("${path.module}/templates/workflow-%s.yaml", try(v.type, ""))
)
) )
} }
cicd_workflow_providers = { cicd_workflow_providers = {

View File

@ -82,7 +82,7 @@ resource "google_iam_workload_identity_pool_provider" "default" {
attribute_mapping = each.value.attribute_mapping attribute_mapping = each.value.attribute_mapping
oidc { oidc {
# Setting an empty list configures allowed_audiences to the url of the provider # Setting an empty list configures allowed_audiences to the url of the provider
allowed_audiences = each.value.custom_settings.allowed_audiences allowed_audiences = each.value.custom_settings.audiences
# If users don't provide an issuer_uri, we set the public one for the plaform choosed. # If users don't provide an issuer_uri, we set the public one for the plaform choosed.
issuer_uri = ( issuer_uri = (
each.value.custom_settings.issuer_uri != null each.value.custom_settings.issuer_uri != null

View File

@ -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.
*/
# tfdoc:file:description Organization-level IAM bindings locals.
locals {
# IAM roles in the org to reset (remove principals)
iam_delete_roles = [
"roles/billing.creator"
]
# domain IAM bindings
iam_domain_bindings = {
"domain:${var.organization.domain}" = {
authoritative = ["roles/browser"]
additive = []
}
}
# human (groups) IAM bindings
iam_group_bindings = {
(local.groups.gcp-billing-admins) = {
authoritative = []
additive = (
local.billing_mode != "org" ? [] : [
"roles/billing.admin",
"roles/billing.costsManager"
]
)
}
(local.groups.gcp-network-admins) = {
authoritative = [
"roles/cloudasset.owner",
"roles/cloudsupport.techSupportEditor",
]
additive = [
"roles/compute.orgFirewallPolicyAdmin",
"roles/compute.xpnAdmin"
]
}
(local.groups.gcp-organization-admins) = {
authoritative = [
"roles/cloudasset.owner",
"roles/cloudsupport.admin",
"roles/compute.osAdminLogin",
"roles/compute.osLoginExternalUser",
"roles/owner",
"roles/resourcemanager.folderAdmin",
"roles/resourcemanager.organizationAdmin",
"roles/resourcemanager.projectCreator",
]
additive = concat(
[
"roles/orgpolicy.policyAdmin"
],
local.billing_mode != "org" ? [] : [
"roles/billing.admin",
"roles/billing.costsManager"
]
)
}
(local.groups.gcp-security-admins) = {
authoritative = [
"roles/cloudasset.owner",
"roles/cloudsupport.techSupportEditor",
"roles/iam.securityReviewer",
"roles/logging.admin",
"roles/securitycenter.admin",
]
additive = [
"roles/accesscontextmanager.policyAdmin",
"roles/iam.organizationRoleAdmin",
"roles/orgpolicy.policyAdmin"
]
}
(local.groups.gcp-support) = {
authoritative = [
"roles/cloudsupport.techSupportEditor",
"roles/logging.viewer",
"roles/monitoring.viewer",
]
additive = []
}
}
# machine (service accounts) IAM bindings, in logical format
# the service account module's "magic" outputs allow us to use dynamic values
iam_sa_bindings = {
(module.automation-tf-bootstrap-sa.iam_email) = {
authoritative = [
"roles/logging.admin",
"roles/resourcemanager.organizationAdmin",
"roles/resourcemanager.projectCreator",
"roles/resourcemanager.projectMover",
]
additive = concat(
[
"roles/iam.organizationRoleAdmin",
"roles/orgpolicy.policyAdmin"
],
local.billing_mode != "org" ? [] : [
"roles/billing.admin",
"roles/billing.costsManager"
]
)
}
(module.automation-tf-resman-sa.iam_email) = {
authoritative = [
"roles/logging.admin",
"roles/resourcemanager.folderAdmin",
"roles/resourcemanager.projectCreator",
"roles/resourcemanager.tagAdmin",
"roles/resourcemanager.tagUser"
]
additive = concat(
[
"roles/orgpolicy.policyAdmin"
],
local.billing_mode != "org" ? [] : [
"roles/billing.admin",
"roles/billing.costsManager"
]
)
}
}
# bootstrap user bindings
iam_user_bootstrap_bindings = var.bootstrap_user == null ? {} : {
"user:${var.bootstrap_user}" = {
authoritative = [
"roles/logging.admin",
"roles/owner",
"roles/resourcemanager.organizationAdmin",
"roles/resourcemanager.projectCreator"
]
additive = (
local.billing_mode != "org" ? [] : [
"roles/billing.admin",
"roles/billing.costsManager"
]
)
}
}
}

View File

@ -17,111 +17,51 @@
# tfdoc:file:description Organization-level IAM. # tfdoc:file:description Organization-level IAM.
locals { locals {
# organization authoritative IAM bindings, in an easy to edit format before # reassemble logical bindings into the formats expected by the module
# they are combined with var.iam a bit further in locals _iam_bindings = merge(
_iam = { local.iam_domain_bindings,
"roles/billing.creator" = [] local.iam_sa_bindings,
"roles/browser" = [ local.iam_user_bootstrap_bindings,
"domain:${var.organization.domain}"
]
"roles/logging.admin" = concat(
[
module.automation-tf-bootstrap-sa.iam_email,
module.automation-tf-resman-sa.iam_email
],
local._iam_bootstrap_user
)
"roles/owner" = local._iam_bootstrap_user
"roles/resourcemanager.folderAdmin" = [
module.automation-tf-resman-sa.iam_email
]
"roles/resourcemanager.organizationAdmin" = concat(
[module.automation-tf-bootstrap-sa.iam_email],
local._iam_bootstrap_user
)
"roles/resourcemanager.projectCreator" = concat(
[
module.automation-tf-bootstrap-sa.iam_email,
module.automation-tf-resman-sa.iam_email
],
local._iam_bootstrap_user
)
"roles/resourcemanager.projectMover" = [
module.automation-tf-bootstrap-sa.iam_email
]
"roles/resourcemanager.tagAdmin" = [
module.automation-tf-resman-sa.iam_email
]
"roles/resourcemanager.tagUser" = [
module.automation-tf-resman-sa.iam_email
]
}
# organization additive IAM bindings, in an easy to edit format before
# they are combined with var.iam_additive a bit further in locals
_iam_additive = merge(
{ {
"roles/accesscontextmanager.policyAdmin" = [ for k, v in local.iam_group_bindings : "group:${k}" => {
local.groups_iam.gcp-security-admins authoritative = []
] additive = v.additive
"roles/compute.orgFirewallPolicyAdmin" = [ }
local.groups_iam.gcp-network-admins }
] )
"roles/compute.xpnAdmin" = [ _iam_bindings_auth = flatten([
local.groups_iam.gcp-network-admins for member, data in local._iam_bindings : [
] for role in data.authoritative : {
# use additive to support cross-org roles for billing member = member
"roles/iam.organizationRoleAdmin" = [ role = role
# uncomment if roles/owner is removed to organization admins }
# local.groups.gcp-organization-admins, ]
local.groups_iam.gcp-security-admins, ])
module.automation-tf-bootstrap-sa.iam_email _iam_bindings_add = flatten([
] for member, data in local._iam_bindings : [
"roles/orgpolicy.policyAdmin" = [ for role in data.additive : {
local.groups_iam.gcp-organization-admins, member = member
local.groups_iam.gcp-security-admins, role = role
module.automation-tf-resman-sa.iam_email }
] ]
# the following is useful if roles/browser is not desirable ])
# "roles/resourcemanager.organizationViewer" = [ group_iam = {
# "domain:${var.organization.domain}" for k, v in local.iam_group_bindings : k => v.authoritative
# ] }
iam = merge(
{
for r in local.iam_delete_roles : r => []
}, },
local.billing_mode == "org" ? { {
"roles/billing.admin" = [ for b in local._iam_bindings_auth : b.role => b.member...
local.groups_iam.gcp-billing-admins, }
local.groups_iam.gcp-organization-admins,
module.automation-tf-bootstrap-sa.iam_email,
module.automation-tf-resman-sa.iam_email
],
"roles/billing.costsManager" = [
local.groups_iam.gcp-billing-admins,
local.groups_iam.gcp-organization-admins,
module.automation-tf-bootstrap-sa.iam_email,
module.automation-tf-resman-sa.iam_email
]
} : {}
) )
_iam_bootstrap_user = ( iam_bindings_additive = {
var.bootstrap_user == null ? [] : ["user:${var.bootstrap_user}"] for b in local._iam_bindings_add : "${b.role}-${b.member}" => {
) member = b.member
iam = { role = b.role
for role in local.iam_roles : role => distinct(concat( }
try(sort(local._iam[role]), []),
try(sort(var.iam[role]), [])
))
} }
iam_additive = {
for role in local.iam_roles_additive : role => distinct(concat(
try(sort(local._iam_additive[role]), []),
try(sort(var.iam_additive[role]), [])
))
}
iam_roles = distinct(concat(
keys(local._iam), keys(var.iam)
))
iam_roles_additive = distinct(concat(
keys(local._iam_additive), keys(var.iam_additive)
))
} }
module "organization" { module "organization" {
@ -129,40 +69,52 @@ module "organization" {
organization_id = "organizations/${var.organization.id}" organization_id = "organizations/${var.organization.id}"
# human (groups) IAM bindings # human (groups) IAM bindings
group_iam = { group_iam = {
(local.groups.gcp-organization-admins) = [ for k, v in local.group_iam :
"roles/cloudasset.owner", k => distinct(concat(v, lookup(var.group_iam, k, [])))
"roles/cloudsupport.admin",
"roles/compute.osAdminLogin",
"roles/compute.osLoginExternalUser",
"roles/owner",
# granted via additive roles
# roles/iam.organizationRoleAdmin
# roles/orgpolicy.policyAdmin
"roles/resourcemanager.folderAdmin",
"roles/resourcemanager.organizationAdmin",
"roles/resourcemanager.projectCreator",
],
(local.groups.gcp-network-admins) = [
"roles/cloudasset.owner",
"roles/cloudsupport.techSupportEditor",
]
(local.groups.gcp-security-admins) = [
"roles/cloudasset.owner",
"roles/cloudsupport.techSupportEditor",
"roles/iam.securityReviewer",
"roles/logging.admin",
"roles/securitycenter.admin",
],
(local.groups.gcp-support) = [
"roles/cloudsupport.techSupportEditor",
"roles/logging.viewer",
"roles/monitoring.viewer",
]
} }
# machine (service accounts) IAM bindings # machine (service accounts) IAM bindings
iam = local.iam iam = merge(
{
for k, v in local.iam : k => distinct(concat(v, lookup(var.iam, k, [])))
},
{
for k, v in var.iam : k => v if lookup(local.iam, k, null) == null
}
)
# additive bindings, used for roles co-managed by different stages # additive bindings, used for roles co-managed by different stages
iam_additive = local.iam_additive iam_bindings_additive = merge(
local.iam_bindings_additive,
var.iam_bindings_additive
)
# delegated role grant for resource manager service account
iam_bindings = {
sa_resman_delegated_iam = {
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])",
join(",", formatlist("'%s'", concat(
[
"roles/accesscontextmanager.policyAdmin",
"roles/compute.orgFirewallPolicyAdmin",
"roles/compute.xpnAdmin",
"roles/orgpolicy.policyAdmin",
"roles/resourcemanager.organizationViewer",
module.organization.custom_role_id[var.custom_role_names.tenant_network_admin]
],
local.billing_mode == "org" ? [
"roles/billing.admin",
"roles/billing.costsManager",
"roles/billing.user",
] : []
)))
)
title = "automation_sa_delegated_grants"
description = "Automation service account delegated grants."
}
}
}
custom_roles = merge(var.custom_roles, { custom_roles = merge(var.custom_roles, {
# this is needed for use in additive IAM bindings, to avoid conflicts # this is needed for use in additive IAM bindings, to avoid conflicts
(var.custom_role_names.organization_iam_admin) = [ (var.custom_role_names.organization_iam_admin) = [
@ -200,36 +152,3 @@ module "organization" {
} }
} }
} }
# assign the custom restricted Organization Admin role to the relevant service
# accounts, with a condition that only enables granting specific roles;
# these roles use additive bindings everywhere to avoid conflicts / permadiffs
resource "google_organization_iam_binding" "org_admin_delegated" {
org_id = var.organization.id
role = module.organization.custom_role_id[var.custom_role_names.organization_iam_admin]
members = [module.automation-tf-resman-sa.iam_email]
condition {
title = "automation_sa_delegated_grants"
description = "Automation service account delegated grants."
expression = format(
"api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])",
join(",", formatlist("'%s'", concat(
[
"roles/accesscontextmanager.policyAdmin",
"roles/compute.orgFirewallPolicyAdmin",
"roles/compute.xpnAdmin",
"roles/orgpolicy.policyAdmin",
"roles/resourcemanager.organizationViewer",
module.organization.custom_role_id[var.custom_role_names.tenant_network_admin]
],
local.billing_mode == "org" ? [
"roles/billing.admin",
"roles/billing.costsManager",
"roles/billing.user",
] : []
)))
)
}
depends_on = [module.organization]
}

View File

@ -22,7 +22,9 @@ locals {
"${path.module}/templates/workflow-${v.type}.yaml", { "${path.module}/templates/workflow-${v.type}.yaml", {
# If users give a list of custom audiences we set by default the first element. # If users give a list of custom audiences we set by default the first element.
# If no audiences are given, we set https://iam.googleapis.com/{PROVIDER_NAME} # If no audiences are given, we set https://iam.googleapis.com/{PROVIDER_NAME}
audience = local.cicd_providers[v["identity_provider"]].audience audiences = try(
local.cicd_providers[v["identity_provider"]].audiences, ""
)
identity_provider = try( identity_provider = try(
local.cicd_providers[v["identity_provider"]].name, "" local.cicd_providers[v["identity_provider"]].name, ""
) )

View File

@ -20,13 +20,14 @@ default:
- "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
variables: variables:
AUDIENCE: ${audience}
GOOGLE_CREDENTIALS: cicd-sa-credentials.json GOOGLE_CREDENTIALS: cicd-sa-credentials.json
FAST_OUTPUTS_BUCKET: ${outputs_bucket} FAST_OUTPUTS_BUCKET: ${outputs_bucket}
FAST_SERVICE_ACCOUNT: ${service_account} FAST_SERVICE_ACCOUNT: ${service_account}
FAST_WIF_PROVIDER: ${identity_provider} FAST_WIF_PROVIDER: ${identity_provider}
SSH_AUTH_SOCK: /tmp/ssh_agent.sock SSH_AUTH_SOCK: /tmp/ssh_agent.sock
%{~ if tf_providers_file != "" ~}
TF_PROVIDERS_FILE: ${tf_providers_file} TF_PROVIDERS_FILE: ${tf_providers_file}
%{~ endif ~}
TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)} TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)}
stages: stages:
@ -38,16 +39,27 @@ stages:
cache: cache:
key: gcp-auth key: gcp-auth
paths: paths:
- .tf-setup - cicd-sa-credentials.json
- token.txt
%{~ if tf_providers_file != "" ~}
- ${tf_providers_file}
%{~ endif ~}
%{~ for f in tf_var_files ~}
- ${f}
%{~ endfor ~}
gcp-auth: gcp-auth:
stage: gcp-auth
id_tokens: id_tokens:
GITLAB_TOKEN: GITLAB_TOKEN:
aud: "$${AUDIENCE}" aud:
%{~ for aud in audiences ~}
- ${aud}
%{~ endfor ~}
image: image:
name: google/cloud-sdk:slim name: google/cloud-sdk:slim
stage: gcp-auth
script: script:
- echo "$${GITLAB_TOKEN}" > token.txt
- | - |
gcloud iam workload-identity-pools create-cred-config \ gcloud iam workload-identity-pools create-cred-config \
$${FAST_WIF_PROVIDER} \ $${FAST_WIF_PROVIDER} \
@ -55,30 +67,27 @@ gcp-auth:
--service-account-token-lifetime-seconds=3600 \ --service-account-token-lifetime-seconds=3600 \
--output-file=$${GOOGLE_CREDENTIALS} \ --output-file=$${GOOGLE_CREDENTIALS} \
--credential-source-file=token.txt --credential-source-file=token.txt
- rm token.txt
artifacts:
untracked: true
tf-files: tf-files:
stage: tf-files dependencies:
- gcp-auth
image: image:
name: google/cloud-sdk:slim name: google/cloud-sdk:slim
stage: tf-files
script: script:
# - gcloud components install -q alpha # - gcloud components install -q alpha
- gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS} - gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS}
- mkdir -p .tf-setup %{~ if tf_providers_file != "" ~}
- | - gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/providers/${tf_providers_file}" ./
gcloud alpha storage cp -r \ %{~ endif ~}
"gs://$${FAST_OUTPUTS_BUCKET}/providers/$${TF_PROVIDERS_FILE}" .tf-setup/ %{~ for f in tf_var_files ~}
- | - gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/tfvars/${f}" ./
gcloud alpha storage cp -r \ %{~ endfor ~}
"gs://$${FAST_OUTPUTS_BUCKET}/tfvars" .tf-setup/ - ls -l
artifacts:
untracked: true
dependencies:
- gcp-auth
tf-plan: tf-plan:
dependencies:
- tf-files
stage: tf-plan stage: tf-plan
# uncomment the following lines and set the SSH key secret for private modules repo # uncomment the following lines and set the SSH key secret for private modules repo
# before_script: # before_script:
@ -89,20 +98,13 @@ tf-plan:
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
script: script:
- cp ".tf-setup/$${TF_PROVIDERS_FILE}" ./
- |
for f in "$${TF_VAR_FILES}"; do
ln -s ".tf-setup/tfvars/$f" ./
done
- terraform init - terraform init
- terraform validate - terraform validate
- terraform plan - terraform plan
artifacts:
untracked: true
dependencies:
- tf-files
tf-apply: tf-apply:
dependencies:
- tf-files
stage: tf-apply stage: tf-apply
# uncomment the following lines and set the SSH key secret for private modules repo # uncomment the following lines and set the SSH key secret for private modules repo
# before_script: # before_script:
@ -113,18 +115,9 @@ tf-apply:
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts # ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts # ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
script: script:
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
- |
for f in $${TF_VAR_FILES}; do
ln -s ".tf-setup/tfvars/$f" ./
done
- terraform init - terraform init
- terraform validate - terraform validate
- terraform apply -input=false -auto-approve - terraform apply -input=false -auto-approve
artifacts:
untracked: true
dependencies:
- tf-files
when: manual when: manual
only: only:
variables: variables:

View File

@ -116,8 +116,8 @@ variable "federated_identity_providers" {
attribute_condition = optional(string) attribute_condition = optional(string)
issuer = string issuer = string
custom_settings = optional(object({ custom_settings = optional(object({
issuer_uri = optional(string) issuer_uri = optional(string)
allowed_audiences = optional(list(string), []) audiences = optional(list(string), [])
}), {}) }), {})
})) }))
default = {} default = {}
@ -129,6 +129,14 @@ variable "federated_identity_providers" {
# } # }
} }
variable "group_iam" {
description = "Organization-level authoritative IAM binding for groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable."
type = map(list(string))
default = {}
nullable = false
}
variable "groups" { variable "groups" {
# https://cloud.google.com/docs/enterprise/setup-checklist # https://cloud.google.com/docs/enterprise/setup-checklist
description = "Group names or emails to grant organization-level permissions. If just the name is provided, the default organization domain is assumed." description = "Group names or emails to grant organization-level permissions. If just the name is provided, the default organization domain is assumed."
@ -150,30 +158,35 @@ variable "groups" {
variable "iam" { variable "iam" {
description = "Organization-level custom IAM settings in role => [principal] format." description = "Organization-level custom IAM settings in role => [principal] format."
type = map(list(string)) type = map(list(string))
nullable = false
default = {} default = {}
} }
variable "iam_additive" { variable "iam_bindings_additive" {
description = "Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings." description = "Organization-level custom additive IAM bindings. Keys are arbitrary."
type = map(list(string)) type = map(object({
default = {} member = string
role = string
condition = optional(object({
expression = string
title = string
description = optional(string)
}))
}))
nullable = false
default = {}
} }
variable "locations" { variable "locations" {
description = "Optional locations for GCS, BigQuery, and logging buckets created here." description = "Optional locations for GCS, BigQuery, and logging buckets created here."
type = object({ type = object({
bq = string bq = optional(string, "EU")
gcs = string gcs = optional(string, "EU")
logging = string logging = optional(string, "global")
pubsub = list(string) pubsub = optional(list(string), [])
}) })
default = {
bq = "EU"
gcs = "EU"
logging = "global"
pubsub = []
}
nullable = false nullable = false
default = {}
} }
# See https://cloud.google.com/architecture/exporting-stackdriver-logging-for-security-and-access-analytics # See https://cloud.google.com/architecture/exporting-stackdriver-logging-for-security-and-access-analytics

View File

@ -342,17 +342,19 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
| [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | <code>google_organization_iam_member</code> | | [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | <code>google_organization_iam_member</code> |
| [branch-gke.tf](./branch-gke.tf) | GKE multitenant stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | | | [branch-gke.tf](./branch-gke.tf) | GKE multitenant stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | |
| [branch-networking.tf](./branch-networking.tf) | Networking stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | | | [branch-networking.tf](./branch-networking.tf) | Networking stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | |
| [branch-project-factory.tf](./branch-project-factory.tf) | Project factory stage resources. | <code>gcs</code> · <code>iam-service-account</code> | <code>google_organization_iam_member</code> | | [branch-project-factory.tf](./branch-project-factory.tf) | Project factory stage resources. | <code>gcs</code> · <code>iam-service-account</code> | |
| [branch-sandbox.tf](./branch-sandbox.tf) | Sandbox stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | <code>google_organization_iam_member</code> | | [branch-sandbox.tf](./branch-sandbox.tf) | Sandbox stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | <code>google_organization_iam_member</code> |
| [branch-security.tf](./branch-security.tf) | Security stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | | | [branch-security.tf](./branch-security.tf) | Security stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | |
| [branch-teams.tf](./branch-teams.tf) | Team stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | | | [branch-teams.tf](./branch-teams.tf) | Team stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | |
| [branch-tenants.tf](./branch-tenants.tf) | Lightweight tenant resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> · <code>organization</code> · <code>project</code> | | | [branch-tenants.tf](./branch-tenants.tf) | Lightweight tenant resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> · <code>project</code> | |
| [cicd-data-platform.tf](./cicd-data-platform.tf) | CI/CD resources for the data platform branch. | <code>iam-service-account</code> · <code>source-repository</code> | | | [cicd-data-platform.tf](./cicd-data-platform.tf) | CI/CD resources for the data platform branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [cicd-gke.tf](./cicd-gke.tf) | CI/CD resources for the data platform branch. | <code>iam-service-account</code> · <code>source-repository</code> | | | [cicd-gke.tf](./cicd-gke.tf) | CI/CD resources for the data platform branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [cicd-networking.tf](./cicd-networking.tf) | CI/CD resources for the networking branch. | <code>iam-service-account</code> · <code>source-repository</code> | | | [cicd-networking.tf](./cicd-networking.tf) | CI/CD resources for the networking branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [cicd-project-factory.tf](./cicd-project-factory.tf) | CI/CD resources for the teams branch. | <code>iam-service-account</code> · <code>source-repository</code> | | | [cicd-project-factory.tf](./cicd-project-factory.tf) | CI/CD resources for the teams branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | <code>iam-service-account</code> · <code>source-repository</code> | | | [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [cicd-teams.tf](./cicd-teams.tf) | CI/CD resources for individual teams. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [main.tf](./main.tf) | Module-level locals and resources. | | | | [main.tf](./main.tf) | Module-level locals and resources. | | |
| [organization-iam.tf](./organization-iam.tf) | Organization-level IAM bindings locals. | | |
| [organization.tf](./organization.tf) | Organization policies. | <code>organization</code> | | | [organization.tf](./organization.tf) | Organization policies. | <code>organization</code> | |
| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | <code>local_file</code> | | [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | <code>local_file</code> |
| [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | <code>google_storage_bucket_object</code> | | [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | <code>google_storage_bucket_object</code> |
@ -364,7 +366,7 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
| name | description | type | required | default | producer | | name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|:---:|
| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | <code title="object&#40;&#123;&#10; outputs_bucket &#61; string&#10; project_id &#61; string&#10; project_number &#61; string&#10; federated_identity_pool &#61; string&#10; federated_identity_providers &#61; map&#40;object&#40;&#123;&#10; audience &#61; string&#10; issuer &#61; string&#10; issuer_uri &#61; string&#10; name &#61; string&#10; principal_tpl &#61; string&#10; principalset_tpl &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> | | [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | <code title="object&#40;&#123;&#10; outputs_bucket &#61; string&#10; project_id &#61; string&#10; project_number &#61; string&#10; federated_identity_pool &#61; string&#10; federated_identity_providers &#61; map&#40;object&#40;&#123;&#10; audiences &#61; list&#40;string&#41;&#10; issuer &#61; string&#10; issuer_uri &#61; string&#10; name &#61; string&#10; principal_tpl &#61; string&#10; principalset_tpl &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> |
| [billing_account](variables.tf#L39) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | <code title="object&#40;&#123;&#10; id &#61; string&#10; is_org_level &#61; optional&#40;bool, true&#41;&#10; no_iam &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> | | [billing_account](variables.tf#L39) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | <code title="object&#40;&#123;&#10; id &#61; string&#10; is_org_level &#61; optional&#40;bool, true&#41;&#10; no_iam &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> |
| [organization](variables.tf#L192) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; number&#10; customer_id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> | | [organization](variables.tf#L192) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; number&#10; customer_id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> |
| [prefix](variables.tf#L216) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>0-bootstrap</code> | | [prefix](variables.tf#L216) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>0-bootstrap</code> |
@ -378,22 +380,23 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
| [outputs_location](variables.tf#L210) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | | | [outputs_location](variables.tf#L210) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | |
| [tag_names](variables.tf#L227) | Customized names for resource management tags. | <code title="object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10; org-policies &#61; string&#10; tenant &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; context &#61; &#34;context&#34;&#10; environment &#61; &#34;environment&#34;&#10; org-policies &#61; &#34;org-policies&#34;&#10; tenant &#61; &#34;tenant&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | | | [tag_names](variables.tf#L227) | Customized names for resource management tags. | <code title="object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10; org-policies &#61; string&#10; tenant &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; context &#61; &#34;context&#34;&#10; environment &#61; &#34;environment&#34;&#10; org-policies &#61; &#34;org-policies&#34;&#10; tenant &#61; &#34;tenant&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | |
| [tags](variables.tf#L248) | Custome secure tags by key name. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; values &#61; optional&#40;map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | | [tags](variables.tf#L248) | Custome secure tags by key name. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; values &#61; optional&#40;map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [team_folders](variables.tf#L269) | Team folders to be created. Format is described in a code comment. | <code title="map&#40;object&#40;&#123;&#10; descriptive_name &#61; string&#10; group_iam &#61; map&#40;list&#40;string&#41;&#41;&#10; impersonation_groups &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> | | | [team_folders](variables.tf#L269) | Team folders to be created. Format is described in a code comment. | <code title="map&#40;object&#40;&#123;&#10; descriptive_name &#61; string&#10; group_iam &#61; map&#40;list&#40;string&#41;&#41;&#10; impersonation_groups &#61; list&#40;string&#41;&#10; cicd &#61; optional&#40;object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> | |
| [tenants](variables.tf#L279) | Lightweight tenant definitions. | <code title="map&#40;object&#40;&#123;&#10; admin_group_email &#61; string&#10; descriptive_name &#61; string&#10; billing_account &#61; optional&#40;string&#41;&#10; organization &#61; optional&#40;object&#40;&#123;&#10; customer_id &#61; string&#10; domain &#61; string&#10; id &#61; number&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | | [tenants](variables.tf#L285) | Lightweight tenant definitions. | <code title="map&#40;object&#40;&#123;&#10; admin_group_email &#61; string&#10; descriptive_name &#61; string&#10; billing_account &#61; optional&#40;string&#41;&#10; organization &#61; optional&#40;object&#40;&#123;&#10; customer_id &#61; string&#10; domain &#61; string&#10; id &#61; number&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [tenants_config](variables.tf#L295) | Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts. | <code title="object&#40;&#123;&#10; core_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; tenant_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; top_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | | | [tenants_config](variables.tf#L301) | Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts. | <code title="object&#40;&#123;&#10; core_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; tenant_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; top_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | |
## Outputs ## Outputs
| name | description | sensitive | consumers | | name | description | sensitive | consumers |
|---|---|:---:|---| |---|---|:---:|---|
| [cicd_repositories](outputs.tf#L213) | WIF configuration for CI/CD repositories. | | | | [cicd_repositories](outputs.tf#L232) | WIF configuration for CI/CD repositories. | | |
| [dataplatform](outputs.tf#L227) | Data for the Data Platform stage. | | | | [dataplatform](outputs.tf#L246) | Data for the Data Platform stage. | | |
| [gke_multitenant](outputs.tf#L243) | Data for the GKE multitenant stage. | | <code>03-gke-multitenant</code> | | [gke_multitenant](outputs.tf#L262) | Data for the GKE multitenant stage. | | <code>03-gke-multitenant</code> |
| [networking](outputs.tf#L264) | Data for the networking stage. | | | | [networking](outputs.tf#L283) | Data for the networking stage. | | |
| [project_factories](outputs.tf#L273) | Data for the project factories stage. | | | | [project_factories](outputs.tf#L292) | Data for the project factories stage. | | |
| [providers](outputs.tf#L288) | Terraform provider files for this stage and dependent stages. | ✓ | <code>02-networking</code> · <code>02-security</code> · <code>03-dataplatform</code> · <code>xx-sandbox</code> · <code>xx-teams</code> | | [providers](outputs.tf#L307) | Terraform provider files for this stage and dependent stages. | ✓ | <code>02-networking</code> · <code>02-security</code> · <code>03-dataplatform</code> · <code>xx-sandbox</code> · <code>xx-teams</code> |
| [sandbox](outputs.tf#L295) | Data for the sandbox stage. | | <code>xx-sandbox</code> | | [sandbox](outputs.tf#L314) | Data for the sandbox stage. | | <code>xx-sandbox</code> |
| [security](outputs.tf#L309) | Data for the networking stage. | | <code>02-security</code> | | [security](outputs.tf#L328) | Data for the networking stage. | | <code>02-security</code> |
| [teams](outputs.tf#L319) | Data for the teams stage. | | | | [team_cicd_repositories](outputs.tf#L338) | WIF configuration for Team CI/CD repositories. | | |
| [tfvars](outputs.tf#L331) | Terraform variable files for the following stages. | ✓ | | | [teams](outputs.tf#L352) | Data for the teams stage. | | |
| [tfvars](outputs.tf#L364) | Terraform variable files for the following stages. | ✓ | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -79,33 +79,3 @@ module "branch-pf-prod-gcs" {
"roles/storage.objectAdmin" = [module.branch-pf-prod-sa.0.iam_email] "roles/storage.objectAdmin" = [module.branch-pf-prod-sa.0.iam_email]
} }
} }
resource "google_organization_iam_member" "org_policy_admin_pf_dev" {
count = var.fast_features.project_factory ? 1 : 0
org_id = var.organization.id
role = "roles/orgpolicy.policyAdmin"
member = module.branch-pf-dev-sa.0.iam_email
condition {
title = "org_policy_tag_pf_scoped_dev"
description = "Org policy tag scoped grant for project factory dev."
expression = <<-END
resource.matchTag('${var.organization.id}/${var.tag_names.context}', 'teams')
&&
resource.matchTag('${var.organization.id}/${var.tag_names.environment}', 'development')
END
}
}
resource "google_organization_iam_member" "org_policy_admin_pf_prod" {
count = var.fast_features.project_factory ? 1 : 0
org_id = var.organization.id
role = "roles/orgpolicy.policyAdmin"
member = module.branch-pf-prod-sa.0.iam_email
condition {
title = "org_policy_tag_pf_scoped_prod"
description = "Org policy tag scoped grant for project factory prod."
expression = <<-END
resource.matchTag('${var.organization.id}/${var.tag_names.context}', 'teams')
END
}
}

View File

@ -90,10 +90,13 @@ module "branch-teams-team-sa" {
display_name = "Terraform team ${each.key} service account." display_name = "Terraform team ${each.key} service account."
prefix = var.prefix prefix = var.prefix
iam = { iam = {
"roles/iam.serviceAccountTokenCreator" = ( "roles/iam.serviceAccountTokenCreator" = concat(
each.value.impersonation_groups == null compact([try(module.branch-teams-team-sa-cicd[each.key].iam_email, null)]),
? [] (
: [for g in each.value.impersonation_groups : "group:${g}"] each.value.impersonation_groups == null
? []
: [for g in each.value.impersonation_groups : "group:${g}"]
)
) )
} }
} }

Some files were not shown because too many files have changed in this diff Show More