Merge branch 'master' into ehorning/support-gcs-object-upload
This commit is contained in:
commit
33d51dbee4
|
@ -28,7 +28,7 @@ jobs:
|
|||
name: "Create tag on master if there was activity in last 24 hours"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: "Check changes and tag"
|
||||
run: |
|
||||
|
|
|
@ -17,9 +17,6 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- ci
|
||||
- lint
|
||||
|
||||
jobs:
|
||||
linting:
|
||||
|
|
|
@ -19,9 +19,6 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- ci
|
||||
- test
|
||||
|
||||
env:
|
||||
GOOGLE_APPLICATION_CREDENTIALS: "/home/runner/credentials.json"
|
||||
|
@ -39,7 +36,7 @@ jobs:
|
|||
|
||||
- uses: hashicorp/setup-terraform@v2
|
||||
with:
|
||||
terraform_version: ${{ env.TERRAFORM_VERSION }}
|
||||
terraform_version: ${{ env.TF_VERSION }}
|
||||
terraform_wrapper: false
|
||||
|
||||
- name: Build lockfile and fetch providers
|
||||
|
@ -76,10 +73,10 @@ jobs:
|
|||
uses: ./.github/actions/fabric-tests
|
||||
with:
|
||||
PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
|
||||
TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }}
|
||||
TERRAFORM_VERSION: ${{ env.TF_VERSION }}
|
||||
|
||||
- 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:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -91,10 +88,10 @@ jobs:
|
|||
uses: ./.github/actions/fabric-tests
|
||||
with:
|
||||
PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
|
||||
TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }}
|
||||
TERRAFORM_VERSION: ${{ env.TF_VERSION }}
|
||||
|
||||
- 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:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -106,10 +103,10 @@ jobs:
|
|||
uses: ./.github/actions/fabric-tests
|
||||
with:
|
||||
PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
|
||||
TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }}
|
||||
TERRAFORM_VERSION: ${{ env.TF_VERSION }}
|
||||
|
||||
- name: Run tests environments
|
||||
run: pytest -vv -n4 tests/blueprints
|
||||
run: pytest -vv -n4 --tb=line tests/blueprints
|
||||
|
||||
modules:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -121,10 +118,10 @@ jobs:
|
|||
uses: ./.github/actions/fabric-tests
|
||||
with:
|
||||
PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
|
||||
TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }}
|
||||
TERRAFORM_VERSION: ${{ env.TF_VERSION }}
|
||||
|
||||
- name: Run tests modules
|
||||
run: pytest -vv -n4 tests/modules
|
||||
run: pytest -vv -n4 --tb=line tests/modules
|
||||
|
||||
fast:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -136,7 +133,7 @@ jobs:
|
|||
uses: ./.github/actions/fabric-tests
|
||||
with:
|
||||
PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
|
||||
TERRAFORM_VERSION: ${{ env.TERRAFORM_VERSION }}
|
||||
TERRAFORM_VERSION: ${{ env.TF_VERSION }}
|
||||
|
||||
- name: Run tests on FAST stages
|
||||
run: pytest -vv -n4 tests/fast
|
||||
run: pytest -vv -n4 --tb=line tests/fast
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
**/.test.lock
|
||||
.idea
|
||||
.vscode
|
||||
.idx/dev.nix
|
||||
backend.tf
|
||||
backend-config.hcl
|
||||
credentials.json
|
||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -6,17 +6,39 @@ All notable changes to this project will be documented in this file.
|
|||
## [Unreleased]
|
||||
<!-- None < 2023-08-09 17:02:13+00:00 -->
|
||||
|
||||
### 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
|
||||
|
|
|
@ -31,7 +31,7 @@ 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)
|
||||
- **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)
|
||||
- **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)
|
||||
|
|
|
@ -29,6 +29,7 @@ import warnings
|
|||
|
||||
import click
|
||||
import google.auth
|
||||
import requests.exceptions
|
||||
|
||||
from google.auth.transport.requests import AuthorizedSession
|
||||
|
||||
|
@ -123,7 +124,8 @@ def fetch(request, delete=False):
|
|||
else:
|
||||
response = HTTP.post(request.url, headers=request.headers,
|
||||
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])
|
||||
try:
|
||||
rdata = json.loads(response.content)
|
||||
|
|
|
@ -2,3 +2,4 @@ click
|
|||
functions-framework
|
||||
google-api-core
|
||||
google-cloud-monitoring
|
||||
requests
|
|
@ -13,21 +13,20 @@ This is the high level diagram:
|
|||
This sample creates\updates several distinct groups of resources:
|
||||
|
||||
- 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.
|
||||
- M4CE target project prerequisites deployed on existing 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.
|
||||
- M4CE target project prerequisites deployed on existing projects.
|
||||
- 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
|
||||
- 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 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 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 or group
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Variables
|
||||
|
||||
| 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(string)</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(string)</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(string)</code> | | <code>[]</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({ billing_account_id = string parent = string })">object({…})</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>"m4ce-host-project-000"</code> |
|
||||
|
||||
|
@ -36,9 +35,7 @@ This sample creates\updates several distinct groups of resources:
|
|||
| 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. | |
|
||||
|
||||
<!-- END TFDOC -->
|
||||
|
||||
## Test
|
||||
|
||||
```hcl
|
||||
|
@ -48,8 +45,8 @@ module "test" {
|
|||
billing_account_id = "1234-ABCD-1234"
|
||||
parent = "folders/1234563"
|
||||
}
|
||||
migration_admin_users = ["user:admin@example.com"]
|
||||
migration_viewer_users = ["user:viewer@example.com"]
|
||||
migration_admin = "user:admin@example.com"
|
||||
migration_viewer = "user:viewer@example.com"
|
||||
migration_target_projects = [module.test-target-project.name]
|
||||
depends_on = [
|
||||
module.test-target-project
|
||||
|
|
|
@ -19,11 +19,11 @@ module "host-project" {
|
|||
: null
|
||||
)
|
||||
name = var.project_name
|
||||
parent = (var.project_create != null
|
||||
parent = (
|
||||
var.project_create != null
|
||||
? var.project_create.parent
|
||||
: null
|
||||
)
|
||||
|
||||
services = [
|
||||
"cloudresourcemanager.googleapis.com",
|
||||
"compute.googleapis.com",
|
||||
|
@ -33,14 +33,24 @@ module "host-project" {
|
|||
"servicecontrol.googleapis.com",
|
||||
"vmmigration.googleapis.com",
|
||||
]
|
||||
|
||||
project_create = var.project_create != null
|
||||
|
||||
iam_additive = {
|
||||
"roles/iam.serviceAccountKeyAdmin" = var.migration_admin_users,
|
||||
"roles/iam.serviceAccountCreator" = var.migration_admin_users,
|
||||
"roles/vmmigration.admin" = var.migration_admin_users,
|
||||
"roles/vmmigration.viewer" = var.migration_viewer_users,
|
||||
iam_bindings_additive = {
|
||||
admin_sa_key_admin = {
|
||||
role = "roles/iam.serviceAccountKeyAdmin"
|
||||
member = var.migration_admin
|
||||
}
|
||||
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"
|
||||
name = each.key
|
||||
project_create = false
|
||||
|
||||
services = [
|
||||
"servicemanagement.googleapis.com",
|
||||
"servicecontrol.googleapis.com",
|
||||
|
@ -64,10 +73,18 @@ module "target-projects" {
|
|||
"cloudresourcemanager.googleapis.com",
|
||||
"compute.googleapis.com"
|
||||
]
|
||||
|
||||
iam_additive = {
|
||||
"roles/resourcemanager.projectIamAdmin" = var.migration_admin_users,
|
||||
"roles/compute.viewer" = var.migration_admin_users,
|
||||
"roles/iam.serviceAccountUser" = var.migration_admin_users
|
||||
iam_bindings_additive = {
|
||||
admin_project_iam_admin = {
|
||||
role = "roles/resourcemanager.projectIamAdmin"
|
||||
member = var.migration_admin
|
||||
}
|
||||
admin_compute_viewer = {
|
||||
role = "roles/compute.viewer"
|
||||
member = var.migration_admin
|
||||
}
|
||||
admin_sa_user = {
|
||||
role = "roles/iam.serviceAccountUser"
|
||||
member = var.migration_admin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
variable "migration_admin_users" {
|
||||
description = "List of users authorized to create a new M4CE sources and perform all other migration operations, in IAM format."
|
||||
type = list(string)
|
||||
variable "migration_admin" {
|
||||
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 = string
|
||||
}
|
||||
|
||||
variable "migration_target_projects" {
|
||||
|
@ -22,10 +22,10 @@ variable "migration_target_projects" {
|
|||
type = list(string)
|
||||
}
|
||||
|
||||
variable "migration_viewer_users" {
|
||||
description = "List of users authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format."
|
||||
type = list(string)
|
||||
default = []
|
||||
variable "migration_viewer" {
|
||||
description = "User or group authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format (`group:foo@example.com`)."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "project_create" {
|
||||
|
|
|
@ -13,34 +13,33 @@ This is the high level diagram:
|
|||
This sample creates\update several distinct groups of resources:
|
||||
|
||||
- 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 target project prerequisites deployed on existing 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 target project prerequisites deployed on existing projects.
|
||||
- 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
|
||||
- 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 viewer role](https://cloud.google.com/migrate/virtual-machines/docs/5.0/how-to/enable-services#using_predefined_roles) to provided user accounts.
|
||||
- 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 [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 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 user or group
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Variables
|
||||
|
||||
| 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(string)</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(string)</code> | ✓ | |
|
||||
| [sharedvpc_host_projects](variables.tf#L45) | List of host projects that share a VPC with the selected target projects. | <code>list(string)</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(string)</code> | | <code>[]</code> |
|
||||
| [project_create](variables.tf#L30) | Parameters for the creation of the new project to host the M4CE backend. | <code title="object({ billing_account_id = string parent = string })">object({…})</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>"m4ce-host-project-000"</code> |
|
||||
| [sharedvpc_host_projects](variables.tf#L46) | List of host projects that share a VPC with the selected target projects. | <code>list(string)</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({ billing_account_id = string parent = string })">object({…})</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>"m4ce-host-project-000"</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
| 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. | |
|
||||
|
||||
<!-- END TFDOC -->
|
||||
## 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.
|
||||
|
||||
## Test
|
||||
|
@ -52,8 +51,8 @@ module "test" {
|
|||
billing_account_id = "1234-ABCD-1234"
|
||||
parent = "folders/1234563"
|
||||
}
|
||||
migration_admin_users = ["user:admin@example.com"]
|
||||
migration_viewer_users = ["user:viewer@example.com"]
|
||||
migration_admin = "user:admin@example.com"
|
||||
migration_viewer = "user:viewer@example.com"
|
||||
migration_target_projects = [module.test-target-project.name]
|
||||
sharedvpc_host_projects = [module.test-sharedvpc-host-project.name]
|
||||
depends_on = [
|
||||
|
|
|
@ -23,7 +23,6 @@ module "host-project" {
|
|||
? var.project_create.parent
|
||||
: null
|
||||
)
|
||||
|
||||
services = [
|
||||
"cloudresourcemanager.googleapis.com",
|
||||
"compute.googleapis.com",
|
||||
|
@ -33,14 +32,24 @@ module "host-project" {
|
|||
"servicecontrol.googleapis.com",
|
||||
"vmmigration.googleapis.com",
|
||||
]
|
||||
|
||||
project_create = var.project_create != null
|
||||
|
||||
iam_additive = {
|
||||
"roles/iam.serviceAccountKeyAdmin" = var.migration_admin_users,
|
||||
"roles/iam.serviceAccountCreator" = var.migration_admin_users,
|
||||
"roles/vmmigration.admin" = var.migration_admin_users,
|
||||
"roles/vmmigration.viewer" = var.migration_viewer_users,
|
||||
iam_bindings_additive = {
|
||||
admin_sa_key_admin = {
|
||||
role = "roles/iam.serviceAccountKeyAdmin"
|
||||
member = var.migration_admin
|
||||
}
|
||||
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" {
|
||||
|
||||
for_each = toset(var.migration_target_projects)
|
||||
source = "../../../../modules/project"
|
||||
name = each.key
|
||||
project_create = false
|
||||
|
||||
services = [
|
||||
"cloudresourcemanager.googleapis.com",
|
||||
"compute.googleapis.com",
|
||||
|
@ -64,21 +71,27 @@ module "target-projects" {
|
|||
"servicemanagement.googleapis.com",
|
||||
"servicecontrol.googleapis.com",
|
||||
]
|
||||
|
||||
iam_additive = {
|
||||
"roles/resourcemanager.projectIamAdmin" = var.migration_admin_users,
|
||||
"roles/iam.serviceAccountUser" = var.migration_admin_users,
|
||||
iam_bindings_additive = {
|
||||
admin_project_iam_admin = {
|
||||
role = "roles/resourcemanager.projectIamAdmin"
|
||||
member = var.migration_admin
|
||||
}
|
||||
admin_sa_user = {
|
||||
role = "roles/iam.serviceAccountUser"
|
||||
member = var.migration_admin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module "sharedvpc_host_project" {
|
||||
|
||||
for_each = toset(var.sharedvpc_host_projects)
|
||||
source = "../../../../modules/project"
|
||||
name = each.key
|
||||
project_create = false
|
||||
|
||||
iam_additive = {
|
||||
"roles/compute.viewer" = var.migration_admin_users,
|
||||
iam_bindings_additive = {
|
||||
admin_compute_viewer = {
|
||||
role = "roles/compute.viewer"
|
||||
member = var.migration_admin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
variable "migration_admin_users" {
|
||||
description = "List of users authorized to create a new M4CE sources and perform all other migration operations, in IAM format."
|
||||
type = list(string)
|
||||
variable "migration_admin" {
|
||||
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 = string
|
||||
}
|
||||
|
||||
variable "migration_target_projects" {
|
||||
|
@ -22,11 +22,12 @@ variable "migration_target_projects" {
|
|||
type = list(string)
|
||||
}
|
||||
|
||||
variable "migration_viewer_users" {
|
||||
description = "List of users authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format."
|
||||
type = list(string)
|
||||
default = []
|
||||
variable "migration_viewer" {
|
||||
description = "User or group authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format (`group:foo@example.com`)."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "project_create" {
|
||||
description = "Parameters for the creation of the new project to host the M4CE backend."
|
||||
type = object({
|
||||
|
|
|
@ -13,21 +13,20 @@ This is the high level diagram:
|
|||
This sample creates several distinct groups of resources:
|
||||
|
||||
- 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
|
||||
- Default VPC network
|
||||
- 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
|
||||
- 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 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 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 or group
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Variables
|
||||
|
||||
| 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(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(string)</code> | | <code>[]</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](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({ billing_account_id = string parent = string })">object({…})</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>"m4ce-host-project-000"</code> |
|
||||
| [vpc_config](variables.tf#L41) | Parameters to create a simple VPC on the M4CE project. | <code title="object({ ip_cidr_range = string, region = string })">object({…})</code> | | <code title="{ ip_cidr_range = "10.200.0.0/20", region = "us-west2" }">{…}</code> |
|
||||
|
@ -37,9 +36,7 @@ This sample creates several distinct groups of resources:
|
|||
| 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. | |
|
||||
|
||||
<!-- END TFDOC -->
|
||||
|
||||
## Test
|
||||
|
||||
```hcl
|
||||
|
@ -49,8 +46,8 @@ module "test" {
|
|||
billing_account_id = "1234-ABCD-1234"
|
||||
parent = "folders/1234563"
|
||||
}
|
||||
migration_admin_users = ["user:admin@example.com"]
|
||||
migration_viewer_users = ["user:viewer@example.com"]
|
||||
migration_admin = "user:admin@example.com"
|
||||
migration_viewer = "user:viewer@example.com"
|
||||
}
|
||||
# tftest modules=5 resources=22
|
||||
```
|
||||
|
|
|
@ -23,7 +23,6 @@ module "landing-project" {
|
|||
? var.project_create.parent
|
||||
: null
|
||||
)
|
||||
|
||||
services = [
|
||||
"cloudresourcemanager.googleapis.com",
|
||||
"compute.googleapis.com",
|
||||
|
@ -34,14 +33,24 @@ module "landing-project" {
|
|||
"servicecontrol.googleapis.com",
|
||||
"vmmigration.googleapis.com"
|
||||
]
|
||||
|
||||
project_create = var.project_create != null
|
||||
|
||||
iam_additive = {
|
||||
"roles/iam.serviceAccountKeyAdmin" = var.migration_admin_users,
|
||||
"roles/iam.serviceAccountCreator" = var.migration_admin_users,
|
||||
"roles/vmmigration.admin" = var.migration_admin_users,
|
||||
"roles/vmmigration.viewer" = var.migration_viewer_users
|
||||
iam_bindings_additive = {
|
||||
admin_sa_key_admin = {
|
||||
role = "roles/iam.serviceAccountKeyAdmin"
|
||||
member = var.migration_admin
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,15 +12,15 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
variable "migration_admin_users" {
|
||||
description = "List of users authorized to create a new M4CE sources and perform all other migration operations, in IAM format."
|
||||
type = list(string)
|
||||
variable "migration_admin" {
|
||||
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 = string
|
||||
}
|
||||
|
||||
variable "migration_viewer_users" {
|
||||
description = "List of users authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format."
|
||||
type = list(string)
|
||||
default = []
|
||||
variable "migration_viewer" {
|
||||
description = "User or group authorized to retrieve information about M4CE in the Google Cloud Console, in IAM format (`group:foo@example.com`)."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "project_create" {
|
||||
|
|
|
@ -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. | |
|
||||
|
||||
<!-- 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
|
||||
```
|
||||
-->
|
||||
|
|
|
@ -32,8 +32,11 @@ module "prj" {
|
|||
"sts.googleapis.com",
|
||||
]
|
||||
project_create = var.project_create != null
|
||||
iam_additive = {
|
||||
"roles/viewer" : [module.sa.iam_email]
|
||||
iam_bindings_additive = {
|
||||
sa_viewer = {
|
||||
member = module.sa.iam_email
|
||||
role = "roles/viewer"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
To deploy the solution on a Shared VPC, you have to configure the `network_config` variable:
|
||||
|
||||
```
|
||||
```hcl
|
||||
network_config = {
|
||||
host_project = "PROJECT_ID"
|
||||
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"
|
||||
cloudsql_psa_range = "10.60.0.0/24"
|
||||
}
|
||||
host_project = "PROJECT_ID"
|
||||
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"
|
||||
cloudsql_psa_range = "10.60.0.0/24"
|
||||
}
|
||||
# tftest skip
|
||||
```
|
||||
|
||||
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.
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Variables
|
||||
|
||||
| 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> | ✓ | |
|
||||
| [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> | ✓ | |
|
||||
| [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(string)</code> | | <code>[]</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({ host_project = string network_self_link = string subnet_self_link = string cloudsql_psa_range = string })">object({…})</code> | | <code>null</code> |
|
||||
| [postgres_database](variables.tf#L34) | `postgres` database. | <code>string</code> | | <code>"guestbook"</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({ billing_account_id = string parent = string })">object({…})</code> | | <code>null</code> |
|
||||
| [regions](variables.tf#L68) | Map of instance_name => location where instances will be deployed. | <code>map(string)</code> | | <code title="{ primary = "europe-west1" replica = "europe-west3" }">{…}</code> |
|
||||
| [service_encryption_keys](variables.tf#L81) | Cloud KMS keys to use to encrypt resources. Provide a key for each reagion configured. | <code>map(string)</code> | | <code>null</code> |
|
||||
| [sql_configuration](variables.tf#L87) | Cloud SQL configuration. | <code title="object({ availability_type = string database_version = string psa_range = string tier = string })">object({…})</code> | | <code title="{ availability_type = "REGIONAL" database_version = "POSTGRES_13" psa_range = "10.60.0.0/16" tier = "db-g1-small" }">{…}</code> |
|
||||
| [sql_users](variables.tf#L103) | Cloud SQL user emails. | <code>list(string)</code> | | <code>[]</code> |
|
||||
|
||||
## 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. | |
|
||||
| [ips](outputs.tf#L36) | IP address of each instance. | |
|
||||
| [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 -->
|
||||
|
||||
## Test
|
||||
|
||||
```hcl
|
||||
module "test" {
|
||||
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"
|
||||
project_id = "project"
|
||||
project_create = {
|
||||
|
@ -180,5 +179,5 @@ module "test" {
|
|||
}
|
||||
prefix = "prefix"
|
||||
}
|
||||
# tftest modules=10 resources=52
|
||||
# tftest modules=9 resources=43
|
||||
```
|
||||
|
|
|
@ -39,7 +39,7 @@ module "db" {
|
|||
}
|
||||
|
||||
resource "google_sql_user" "users" {
|
||||
for_each = toset(var.data_eng_principals)
|
||||
for_each = toset(var.sql_users)
|
||||
project = module.project.project_id
|
||||
name = each.value
|
||||
instance = module.db.name
|
||||
|
@ -47,8 +47,7 @@ resource "google_sql_user" "users" {
|
|||
}
|
||||
|
||||
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
|
||||
name = regex("(.+)(.gserviceaccount)", module.service-account-sql.email)[0]
|
||||
instance = module.db.name
|
||||
|
|
|
@ -15,54 +15,26 @@
|
|||
*/
|
||||
|
||||
locals {
|
||||
data_eng_principals_iam = [
|
||||
for k in var.data_eng_principals :
|
||||
"user:${k}"
|
||||
]
|
||||
|
||||
iam = {
|
||||
# GCS roles
|
||||
"roles/storage.objectAdmin" = [
|
||||
"serviceAccount:${module.project.service_accounts.robots.sql}",
|
||||
module.service-account-gcs.iam_email,
|
||||
iam_roles = {
|
||||
data_eng = [
|
||||
"roles/owner"
|
||||
]
|
||||
# CloudSQL
|
||||
"roles/cloudsql.admin" = local.data_eng_principals_iam
|
||||
"roles/cloudsql.client" = concat(
|
||||
local.data_eng_principals_iam,
|
||||
[module.service-account-sql.iam_email]
|
||||
)
|
||||
"roles/cloudsql.instanceUser" = concat(
|
||||
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}"
|
||||
sql_robot = [
|
||||
"roles/compute.networkUser",
|
||||
"roles/storage.objectAdmin"
|
||||
]
|
||||
sql_sa = [
|
||||
"roles/cloudsql.client",
|
||||
"roles/cloudsql.instanceUser"
|
||||
]
|
||||
}
|
||||
|
||||
shared_vpc_project = try(var.network_config.host_project, null)
|
||||
use_shared_vpc = var.network_config != null
|
||||
|
||||
subnet = (
|
||||
local.use_shared_vpc
|
||||
? var.network_config.subnet_self_link
|
||||
: values(module.vpc.0.subnet_self_links)[0]
|
||||
)
|
||||
use_shared_vpc = var.network_config != null
|
||||
vpc_self_link = (
|
||||
local.use_shared_vpc
|
||||
? var.network_config.network_self_link
|
||||
|
@ -77,8 +49,26 @@ module "project" {
|
|||
billing_account = try(var.project_create.billing_account_id, null)
|
||||
project_create = var.project_create != null
|
||||
prefix = var.project_create == null ? null : var.prefix
|
||||
iam = var.project_create != null ? local.iam : {}
|
||||
iam_additive = var.project_create == null ? local.iam : {}
|
||||
iam_bindings_additive = merge(
|
||||
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 = [
|
||||
"cloudkms.googleapis.com",
|
||||
"compute.googleapis.com",
|
||||
|
@ -92,12 +82,10 @@ module "project" {
|
|||
"storage.googleapis.com",
|
||||
"storage-component.googleapis.com",
|
||||
]
|
||||
|
||||
shared_vpc_service_config = local.shared_vpc_project == null ? null : {
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
}
|
||||
|
||||
service_encryption_key_ids = {
|
||||
compute = try(values(var.service_encryption_keys), [])
|
||||
sql = try(values(var.service_encryption_keys), [])
|
||||
|
@ -120,7 +108,6 @@ module "vpc" {
|
|||
region = var.regions.primary
|
||||
}
|
||||
]
|
||||
|
||||
psa_config = {
|
||||
ranges = { cloud-sql = var.sql_configuration.psa_range }
|
||||
routes = null
|
||||
|
@ -145,3 +132,14 @@ module "nat" {
|
|||
name = "${var.prefix}-default"
|
||||
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
|
||||
}
|
||||
|
|
|
@ -43,10 +43,7 @@ output "project_id" {
|
|||
value = module.project.project_id
|
||||
}
|
||||
|
||||
output "service_accounts" {
|
||||
description = "Service Accounts."
|
||||
value = {
|
||||
"gcs" = module.service-account-gcs.email
|
||||
"sql" = module.service-account-sql.email
|
||||
}
|
||||
output "service_account" {
|
||||
description = "SQL client service Accounts."
|
||||
value = module.service-account-sql.email
|
||||
}
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
variable "data_eng_principals" {
|
||||
description = "Groups with Service Account Token creator role on service accounts in IAM format, only user supported on CloudSQL, eg 'user@domain.com'."
|
||||
type = list(string)
|
||||
default = []
|
||||
variable "data_eng_principal" {
|
||||
description = "Group or user in IAM format (`group:foo@example.com`) with permissions to access resources and impersonate service accounts."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "network_config" {
|
||||
|
@ -99,3 +99,9 @@ variable "sql_configuration" {
|
|||
tier = "db-g1-small"
|
||||
}
|
||||
}
|
||||
|
||||
variable "sql_users" {
|
||||
description = "Cloud SQL user emails."
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
|
|
@ -1,34 +1,39 @@
|
|||
# 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:
|
||||
- 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.
|
||||
|
||||
The solution will use:
|
||||
- Cloud Composer
|
||||
- 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.
|
||||
|
||||
- Cloud Composer
|
||||
- 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:
|
||||
- Shared VPC
|
||||
- Cloud KMS CMEK keys
|
||||
|
||||
- Shared VPC
|
||||
- Cloud KMS CMEK keys
|
||||
|
||||
This is the high level diagram:
|
||||
|
||||
![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.
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
$ terraform init
|
||||
terraform init
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
$ terraform apply
|
||||
terraform apply
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
- Cloudsql: `10.20.10.0/24`
|
||||
- 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.
|
||||
## 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:
|
||||
|
||||
```tfvars
|
||||
network_config = {
|
||||
host_project = "PROJECT"
|
||||
|
@ -68,42 +78,47 @@ network_config = {
|
|||
services = "services"
|
||||
}
|
||||
}
|
||||
# tftest skip
|
||||
```
|
||||
|
||||
Make sure that:
|
||||
|
||||
- The GKE API (`container.googleapis.com`) is enabled in the VPC host project.
|
||||
- The subnet has secondary ranges configured with 2 ranges:
|
||||
- pods: `/22` example: `10.10.8.0/22`
|
||||
- services = `/24` example: 10.10.12.0/24`
|
||||
- pods: `/22` example: `10.10.8.0/22`
|
||||
- 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)
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
```tfvars
|
||||
service_encryption_keys = {
|
||||
`europe/west1` = `projects/PROJECT/locations/REGION/keyRings/KR_NAME/cryptoKeys/KEY_NAME`
|
||||
}
|
||||
# tftest skip
|
||||
```
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Variables
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [prefix](variables.tf#L82) | 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> | ✓ | |
|
||||
| [prefix](variables.tf#L83) | Prefix used for resource names. | <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({ environment_size = string software_config = any workloads_config = object({ scheduler = object( { cpu = number memory_gb = number storage_gb = number count = number } ) web_server = object( { cpu = number memory_gb = number storage_gb = number } ) worker = object( { cpu = number memory_gb = number storage_gb = number min_count = number max_count = number } ) }) })">object({…})</code> | | <code title="{ environment_size = "ENVIRONMENT_SIZE_SMALL" software_config = { image_version = "composer-2-airflow-2" } workloads_config = null }">{…}</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(list(string))</code> | | <code>null</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({ host_project = string network_self_link = string subnet_self_link = string composer_ip_ranges = object({ cloudsql = string gke_master = string }) composer_secondary_ranges = object({ pods = string services = string }) })">object({…})</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({ billing_account_id = string parent = string })">object({…})</code> | | <code>null</code> |
|
||||
| [region](variables.tf#L105) | Reagion where instances will be deployed. | <code>string</code> | | <code>"europe-west1"</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(string)</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(list(string))</code> | | <code>{}</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({ host_project = string network_self_link = string subnet_self_link = string composer_ip_ranges = object({ cloudsql = string gke_master = string }) composer_secondary_ranges = object({ pods = string services = string }) })">object({…})</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({ billing_account_id = string parent = string })">object({…})</code> | | <code>null</code> |
|
||||
| [region](variables.tf#L106) | Reagion where instances will be deployed. | <code>string</code> | | <code>"europe-west1"</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(string)</code> | | <code>null</code> |
|
||||
|
||||
## 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_dag_gcs](outputs.tf#L22) | The Cloud Storage prefix of the DAGs for the Cloud Composer environment. | |
|
||||
|
||||
<!-- END TFDOC -->
|
||||
## Test
|
||||
|
||||
|
|
|
@ -15,14 +15,8 @@
|
|||
*/
|
||||
|
||||
locals {
|
||||
iam = merge(
|
||||
{
|
||||
"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
|
||||
# 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
|
||||
_shared_vpc_bindings = {
|
||||
"roles/compute.networkUser" = [
|
||||
"prj-cloudservices", "prj-robot-gke"
|
||||
|
@ -34,11 +28,16 @@ locals {
|
|||
"prj-robot-gke"
|
||||
]
|
||||
}
|
||||
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}"
|
||||
}
|
||||
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
|
||||
)
|
||||
# reassemble in a format suitable for for_each
|
||||
shared_vpc_bindings_map = {
|
||||
for binding in flatten([
|
||||
|
@ -47,27 +46,24 @@ locals {
|
|||
]
|
||||
]) : "${binding.role}-${binding.member}" => binding
|
||||
}
|
||||
|
||||
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 = (
|
||||
local.use_shared_vpc
|
||||
? var.network_config.network_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" {
|
||||
|
@ -77,8 +73,24 @@ module "project" {
|
|||
billing_account = try(var.project_create.billing_account_id, null)
|
||||
project_create = var.project_create != null
|
||||
prefix = var.project_create == null ? null : var.prefix
|
||||
iam = var.project_create != null ? local.iam : {}
|
||||
iam_additive = var.project_create == null ? local.iam : {}
|
||||
iam_bindings_additive = merge(
|
||||
{
|
||||
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 = [
|
||||
"artifactregistry.googleapis.com",
|
||||
"cloudkms.googleapis.com",
|
||||
|
@ -94,19 +106,13 @@ module "project" {
|
|||
"storage.googleapis.com",
|
||||
"storage-component.googleapis.com",
|
||||
]
|
||||
|
||||
shared_vpc_service_config = local.shared_vpc_project == null ? null : {
|
||||
attach = true
|
||||
host_project = local.shared_vpc_project
|
||||
}
|
||||
|
||||
service_encryption_key_ids = {
|
||||
composer = [try(lookup(var.service_encryption_keys, var.region, null), null)]
|
||||
}
|
||||
|
||||
service_config = {
|
||||
disable_on_destroy = false, disable_dependent_services = false
|
||||
}
|
||||
}
|
||||
|
||||
module "vpc" {
|
||||
|
|
|
@ -55,10 +55,11 @@ variable "composer_config" {
|
|||
}
|
||||
}
|
||||
|
||||
variable "iam_groups_map" {
|
||||
description = "Map of Role => groups to be added on the project. Example: { \"roles/composer.admin\" = [\"group:gcp-data-engineers@example.com\"]}."
|
||||
variable "iam_bindings_additive" {
|
||||
description = "Map of Role => principal in IAM format (`group:foo@example.org`) to be added on the project."
|
||||
type = map(list(string))
|
||||
default = null
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "network_config" {
|
||||
|
|
|
@ -15,21 +15,28 @@
|
|||
# tfdoc:file:description drop off project and resources.
|
||||
|
||||
locals {
|
||||
iam_drp = {
|
||||
"roles/bigquery.dataEditor" = [
|
||||
module.drop-sa-bq-0.iam_email, local.groups_iam.data-engineers
|
||||
drp_iam = {
|
||||
data_engineers = [
|
||||
"roles/bigquery.dataEditor",
|
||||
"roles/bigquery.user"
|
||||
]
|
||||
"roles/bigquery.user" = [
|
||||
module.load-sa-df-0.iam_email, local.groups_iam.data-engineers
|
||||
sa_drop_bq = [
|
||||
"roles/bigquery.dataEditor"
|
||||
]
|
||||
"roles/pubsub.publisher" = [module.drop-sa-ps-0.iam_email]
|
||||
"roles/pubsub.subscriber" = [
|
||||
module.orch-sa-cmp-0.iam_email, module.load-sa-df-0.iam_email
|
||||
sa_drop_cs = [
|
||||
"roles/storage.objectCreator"
|
||||
]
|
||||
"roles/storage.objectCreator" = [module.drop-sa-cs-0.iam_email]
|
||||
"roles/storage.objectViewer" = [module.orch-sa-cmp-0.iam_email]
|
||||
"roles/storage.objectAdmin" = [
|
||||
module.load-sa-df-0.iam_email, module.load-sa-df-0.iam_email
|
||||
sa_drop_ps = [
|
||||
"roles/pubsub.publisher"
|
||||
]
|
||||
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
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
name = var.project_config.billing_account_id == null ? var.project_config.project_ids.drop : "${var.project_config.project_ids.drop}${local.project_suffix}"
|
||||
iam = var.project_config.billing_account_id != null ? local.iam_drp : null
|
||||
iam_additive = var.project_config.billing_account_id == null ? local.iam_drp : null
|
||||
prefix = local.use_projects ? null : var.prefix
|
||||
name = (
|
||||
local.use_projects
|
||||
? 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, [
|
||||
"bigquery.googleapis.com",
|
||||
"bigqueryreservation.googleapis.com",
|
||||
|
@ -59,8 +70,6 @@ module "drop-project" {
|
|||
}
|
||||
}
|
||||
|
||||
# Cloud Storage
|
||||
|
||||
module "drop-sa-cs-0" {
|
||||
source = "../../../modules/iam-service-account"
|
||||
project_id = module.drop-project.project_id
|
||||
|
@ -89,8 +98,6 @@ module "drop-cs-0" {
|
|||
# }
|
||||
}
|
||||
|
||||
# PubSub
|
||||
|
||||
module "drop-sa-ps-0" {
|
||||
source = "../../../modules/iam-service-account"
|
||||
project_id = module.drop-project.project_id
|
||||
|
@ -111,8 +118,6 @@ module "drop-ps-0" {
|
|||
kms_key = try(local.service_encryption_keys.pubsub, null)
|
||||
}
|
||||
|
||||
# BigQuery
|
||||
|
||||
module "drop-sa-bq-0" {
|
||||
source = "../../../modules/iam-service-account"
|
||||
project_id = module.drop-project.project_id
|
||||
|
|
|
@ -15,46 +15,38 @@
|
|||
# tfdoc:file:description Load project and VPC.
|
||||
|
||||
locals {
|
||||
iam_load = {
|
||||
"roles/bigquery.jobUser" = [module.load-sa-df-0.iam_email]
|
||||
"roles/dataflow.admin" = [
|
||||
module.orch-sa-cmp-0.iam_email,
|
||||
module.load-sa-df-0.iam_email,
|
||||
local.groups_iam.data-engineers
|
||||
load_iam = {
|
||||
data_engineers = [
|
||||
"roles/dataflow.admin"
|
||||
]
|
||||
"roles/dataflow.developer" = [
|
||||
local.groups_iam.data-engineers
|
||||
robots_dataflow_load = [
|
||||
"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" {
|
||||
source = "../../../modules/project"
|
||||
parent = var.project_config.parent
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
name = var.project_config.billing_account_id == null ? var.project_config.project_ids.load : "${var.project_config.project_ids.load}${local.project_suffix}"
|
||||
iam = var.project_config.billing_account_id != null ? local.iam_load : null
|
||||
iam_additive = var.project_config.billing_account_id == null ? local.iam_load : null
|
||||
prefix = local.use_projects ? null : var.prefix
|
||||
name = (
|
||||
local.use_projects
|
||||
? 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, [
|
||||
"bigquery.googleapis.com",
|
||||
"bigqueryreservation.googleapis.com",
|
||||
|
@ -106,8 +98,6 @@ module "load-cs-df-0" {
|
|||
encryption_key = try(local.service_encryption_keys.storage, null)
|
||||
}
|
||||
|
||||
# internal VPC resources
|
||||
|
||||
module "load-vpc" {
|
||||
source = "../../../modules/net-vpc"
|
||||
count = local.use_shared_vpc ? 0 : 1
|
||||
|
|
|
@ -15,58 +15,39 @@
|
|||
# tfdoc:file:description Orchestration project and VPC.
|
||||
|
||||
locals {
|
||||
iam_orch = {
|
||||
"roles/artifactregistry.admin" = [local.groups_iam.data-engineers]
|
||||
"roles/artifactregistry.reader" = [module.load-sa-df-0.iam_email]
|
||||
"roles/bigquery.dataEditor" = [
|
||||
module.load-sa-df-0.iam_email,
|
||||
module.transf-sa-df-0.iam_email,
|
||||
local.groups_iam.data-engineers
|
||||
orch_iam = {
|
||||
data_engineers = [
|
||||
"roles/artifactregistry.admin",
|
||||
"roles/bigquery.dataEditor",
|
||||
"roles/bigquery.jobUser",
|
||||
"roles/cloudbuild.builds.editor",
|
||||
"roles/composer.environmentAndStorageObjectAdmin",
|
||||
"roles/iam.serviceAccountUser",
|
||||
"roles/iap.httpsResourceAccessor",
|
||||
"roles/serviceusage.serviceUsageConsumer"
|
||||
]
|
||||
"roles/bigquery.jobUser" = [
|
||||
module.orch-sa-cmp-0.iam_email,
|
||||
local.groups_iam.data-engineers
|
||||
robots_cloudbuild = [
|
||||
"roles/storage.objectAdmin"
|
||||
]
|
||||
"roles/cloudbuild.builds.editor" = [local.groups_iam.data-engineers]
|
||||
"roles/cloudbuild.serviceAgent" = [module.orch-sa-df-build.iam_email]
|
||||
"roles/composer.admin" = [local.groups_iam.data-engineers]
|
||||
"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}"
|
||||
robots_composer = [
|
||||
"roles/composer.ServiceAgentV2Ext",
|
||||
"roles/storage.objectAdmin"
|
||||
]
|
||||
"roles/composer.worker" = [
|
||||
module.orch-sa-cmp-0.iam_email
|
||||
sa_load = [
|
||||
"roles/artifactregistry.reader",
|
||||
"roles/bigquery.dataEditor",
|
||||
"roles/storage.objectViewer"
|
||||
]
|
||||
"roles/iam.serviceAccountUser" = [
|
||||
module.orch-sa-cmp-0.iam_email, local.groups_iam.data-engineers
|
||||
sa_orch = [
|
||||
"roles/bigquery.jobUser",
|
||||
"roles/composer.worker",
|
||||
"roles/iam.serviceAccountUser",
|
||||
"roles/storage.objectAdmin"
|
||||
]
|
||||
"roles/iap.httpsResourceAccessor" = [local.groups_iam.data-engineers]
|
||||
"roles/serviceusage.serviceUsageConsumer" = [local.groups_iam.data-engineers]
|
||||
"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
|
||||
sa_transf_df = [
|
||||
"roles/bigquery.dataEditor"
|
||||
]
|
||||
"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" {
|
||||
|
@ -74,15 +55,17 @@ module "orch-project" {
|
|||
parent = var.project_config.parent
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
prefix = local.use_projects ? null : var.prefix
|
||||
name = (
|
||||
var.project_config.billing_account_id == null
|
||||
local.use_projects
|
||||
? var.project_config.project_ids.orc
|
||||
: "${var.project_config.project_ids.orc}${local.project_suffix}"
|
||||
)
|
||||
iam = var.project_config.billing_account_id != null ? local.iam_orch : null
|
||||
iam_additive = var.project_config.billing_account_id == null ? local.iam_orch : null
|
||||
oslogin = false
|
||||
iam = local.use_projects ? {} : local.orch_iam_auth
|
||||
iam_bindings_additive = !local.use_projects ? {} : local.orch_iam_additive
|
||||
compute_metadata = {
|
||||
enable-oslogin = "false"
|
||||
}
|
||||
services = concat(var.project_services, [
|
||||
"artifactregistry.googleapis.com",
|
||||
"bigquery.googleapis.com",
|
||||
|
@ -112,8 +95,6 @@ module "orch-project" {
|
|||
}
|
||||
}
|
||||
|
||||
# Cloud Storage
|
||||
|
||||
module "orch-cs-0" {
|
||||
source = "../../../modules/gcs"
|
||||
project_id = module.orch-project.project_id
|
||||
|
@ -124,8 +105,6 @@ module "orch-cs-0" {
|
|||
encryption_key = try(local.service_encryption_keys.storage, null)
|
||||
}
|
||||
|
||||
# internal VPC resources
|
||||
|
||||
module "orch-vpc" {
|
||||
source = "../../../modules/net-vpc"
|
||||
count = local.use_shared_vpc ? 0 : 1
|
||||
|
|
|
@ -15,29 +15,25 @@
|
|||
# tfdoc:file:description Transformation project and VPC.
|
||||
|
||||
locals {
|
||||
iam_trf = {
|
||||
"roles/bigquery.jobUser" = [
|
||||
module.transf-sa-bq-0.iam_email, local.groups_iam.data-engineers
|
||||
trf_iam = {
|
||||
data_engineers = [
|
||||
"roles/bigquery.jobUser",
|
||||
"roles/dataflow.admin"
|
||||
]
|
||||
"roles/dataflow.admin" = [
|
||||
module.orch-sa-cmp-0.iam_email, local.groups_iam.data-engineers
|
||||
robots_dataflow_trf = [
|
||||
"roles/storage.objectAdmin"
|
||||
]
|
||||
"roles/dataflow.worker" = [module.transf-sa-df-0.iam_email]
|
||||
"roles/storage.objectAdmin" = [
|
||||
module.transf-sa-df-0.iam_email,
|
||||
"serviceAccount:${module.transf-project.service_accounts.robots.dataflow}"
|
||||
sa_orch = [
|
||||
"roles/dataflow.admin"
|
||||
]
|
||||
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" {
|
||||
|
@ -45,10 +41,14 @@ module "transf-project" {
|
|||
parent = var.project_config.parent
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
name = var.project_config.billing_account_id == null ? var.project_config.project_ids.trf : "${var.project_config.project_ids.trf}${local.project_suffix}"
|
||||
iam = var.project_config.billing_account_id != null ? local.iam_trf : null
|
||||
iam_additive = var.project_config.billing_account_id == null ? local.iam_trf : null
|
||||
prefix = local.use_projects ? null : var.prefix
|
||||
name = (
|
||||
local.use_projects
|
||||
? 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, [
|
||||
"bigquery.googleapis.com",
|
||||
"bigqueryreservation.googleapis.com",
|
||||
|
@ -72,8 +72,6 @@ module "transf-project" {
|
|||
}
|
||||
}
|
||||
|
||||
# Cloud Storage
|
||||
|
||||
module "transf-sa-df-0" {
|
||||
source = "../../../modules/iam-service-account"
|
||||
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)
|
||||
}
|
||||
|
||||
# BigQuery
|
||||
|
||||
module "transf-sa-bq-0" {
|
||||
source = "../../../modules/iam-service-account"
|
||||
project_id = module.transf-project.project_id
|
||||
|
@ -120,8 +116,6 @@ module "transf-sa-bq-0" {
|
|||
}
|
||||
}
|
||||
|
||||
# internal VPC resources
|
||||
|
||||
module "transf-vpc" {
|
||||
source = "../../../modules/net-vpc"
|
||||
count = local.use_shared_vpc ? 0 : 1
|
||||
|
|
|
@ -15,61 +15,48 @@
|
|||
# tfdoc:file:description Data Warehouse projects.
|
||||
|
||||
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 = {
|
||||
"roles/bigquery.dataOwner" = [
|
||||
module.transf-sa-df-0.iam_email,
|
||||
module.transf-sa-bq-0.iam_email,
|
||||
data_analysts = [
|
||||
"roles/bigquery.dataViewer",
|
||||
"roles/bigquery.jobUser",
|
||||
"roles/datacatalog.viewer",
|
||||
"roles/storage.objectViewer"
|
||||
]
|
||||
"roles/bigquery.dataViewer" = [
|
||||
local.groups_iam.data-analysts,
|
||||
local.groups_iam.data-engineers
|
||||
data_engineers = [
|
||||
"roles/bigquery.dataViewer",
|
||||
"roles/bigquery.jobUser",
|
||||
"roles/datacatalog.viewer",
|
||||
"roles/storage.objectViewer"
|
||||
]
|
||||
"roles/bigquery.jobUser" = [
|
||||
module.transf-sa-bq-0.iam_email,
|
||||
local.groups_iam.data-analysts,
|
||||
local.groups_iam.data-engineers
|
||||
sa_transf_bq = [
|
||||
"roles/bigquery.dataOwner",
|
||||
"roles/bigquery.jobUser"
|
||||
]
|
||||
"roles/datacatalog.tagTemplateViewer" = [
|
||||
local.groups_iam.data-analysts, local.groups_iam.data-engineers
|
||||
sa_transf_df = [
|
||||
"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
|
||||
|
@ -79,11 +66,15 @@ module "dwh-lnd-project" {
|
|||
parent = var.project_config.parent
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
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}"
|
||||
iam = var.project_config.billing_account_id != null ? local.dwh_lnd_iam : {}
|
||||
iam_additive = var.project_config.billing_account_id == null ? local.dwh_lnd_iam : {}
|
||||
services = local.dwh_services
|
||||
prefix = local.use_projects ? null : var.prefix
|
||||
name = (
|
||||
local.use_projects
|
||||
? var.project_config.project_ids.dwh-lnd
|
||||
: "${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 = {
|
||||
bq = [try(local.service_encryption_keys.bq, null)]
|
||||
storage = [try(local.service_encryption_keys.storage, null)]
|
||||
|
@ -95,11 +86,15 @@ module "dwh-cur-project" {
|
|||
parent = var.project_config.parent
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
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}"
|
||||
iam = var.project_config.billing_account_id != null ? local.dwh_iam : {}
|
||||
iam_additive = var.project_config.billing_account_id == null ? local.dwh_iam : {}
|
||||
services = local.dwh_services
|
||||
prefix = local.use_projects ? null : var.prefix
|
||||
name = (
|
||||
local.use_projects
|
||||
? var.project_config.project_ids.dwh-cur
|
||||
: "${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 = {
|
||||
bq = [try(local.service_encryption_keys.bq, null)]
|
||||
storage = [try(local.service_encryption_keys.storage, null)]
|
||||
|
@ -111,19 +106,21 @@ module "dwh-conf-project" {
|
|||
parent = var.project_config.parent
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
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}"
|
||||
iam = var.project_config.billing_account_id != null ? local.dwh_iam : null
|
||||
iam_additive = var.project_config.billing_account_id == null ? local.dwh_iam : null
|
||||
services = local.dwh_services
|
||||
prefix = local.use_projects ? null : var.prefix
|
||||
name = (
|
||||
local.use_projects
|
||||
? var.project_config.project_ids.dwh-conf
|
||||
: "${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 = {
|
||||
bq = [try(local.service_encryption_keys.bq, null)]
|
||||
storage = [try(local.service_encryption_keys.storage, null)]
|
||||
}
|
||||
}
|
||||
|
||||
# Bigquery
|
||||
|
||||
module "dwh-lnd-bq-0" {
|
||||
source = "../../../modules/bigquery-dataset"
|
||||
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)
|
||||
}
|
||||
|
||||
# Cloud storage
|
||||
|
||||
module "dwh-lnd-cs-0" {
|
||||
source = "../../../modules/gcs"
|
||||
project_id = module.dwh-lnd-project.project_id
|
||||
|
|
|
@ -15,47 +15,56 @@
|
|||
# tfdoc:file:description common project.
|
||||
|
||||
locals {
|
||||
iam_common = {
|
||||
"roles/dlp.admin" = [local.groups_iam.data-security]
|
||||
"roles/dlp.estimatesAdmin" = [local.groups_iam.data-engineers]
|
||||
"roles/dlp.reader" = [local.groups_iam.data-engineers]
|
||||
"roles/dlp.user" = [
|
||||
module.load-sa-df-0.iam_email,
|
||||
module.transf-sa-df-0.iam_email,
|
||||
local.groups_iam.data-engineers
|
||||
cmn_iam = {
|
||||
data_analysts = [
|
||||
# uncomment if access to all tagged columns is needed
|
||||
# "roles/datacatalog.categoryFineGrainedReader",
|
||||
"roles/datacatalog.viewer"
|
||||
]
|
||||
"roles/datacatalog.admin" = [local.groups_iam.data-security]
|
||||
"roles/datacatalog.viewer" = [
|
||||
module.load-sa-df-0.iam_email,
|
||||
module.transf-sa-df-0.iam_email,
|
||||
module.transf-sa-bq-0.iam_email,
|
||||
local.groups_iam.data-analysts
|
||||
data_engineers = [
|
||||
"roles/dlp.estimatesAdmin",
|
||||
"roles/dlp.reader",
|
||||
"roles/dlp.user"
|
||||
]
|
||||
"roles/datacatalog.categoryFineGrainedReader" = [
|
||||
module.transf-sa-df-0.iam_email,
|
||||
module.transf-sa-bq-0.iam_email,
|
||||
# Uncomment if you want to grant access to `data-analyst` to all columns tagged.
|
||||
# local.groups_iam.data-analysts
|
||||
data_security = [
|
||||
"roles/datacatalog.admin",
|
||||
"roles/dlp.admin"
|
||||
]
|
||||
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" {
|
||||
source = "../../../modules/project"
|
||||
parent = var.project_config.parent
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
name = var.project_config.billing_account_id == null ? var.project_config.project_ids.common : "${var.project_config.project_ids.common}${local.project_suffix}"
|
||||
iam = var.project_config.billing_account_id != null ? local.iam_common : null
|
||||
iam_additive = var.project_config.billing_account_id == null ? local.iam_common : null
|
||||
prefix = local.use_projects ? null : var.prefix
|
||||
name = (
|
||||
local.use_projects
|
||||
? 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, [
|
||||
"datacatalog.googleapis.com",
|
||||
"dlp.googleapis.com",
|
||||
])
|
||||
}
|
||||
|
||||
# Data Catalog Policy tag
|
||||
|
||||
module "common-datacatalog" {
|
||||
source = "../../../modules/data-catalog-policy-tag"
|
||||
project_id = module.common-project.project_id
|
||||
|
@ -64,7 +73,8 @@ module "common-datacatalog" {
|
|||
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" {
|
||||
# source = "../../../modules/kms"
|
||||
|
|
|
@ -19,6 +19,10 @@ module "exp-project" {
|
|||
parent = var.project_config.parent
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
name = var.project_config.billing_account_id == null ? var.project_config.project_ids.exp : "${var.project_config.project_ids.exp}${local.project_suffix}"
|
||||
prefix = local.use_projects ? null : var.prefix
|
||||
name = (
|
||||
local.use_projects
|
||||
? var.project_config.project_ids.exp
|
||||
: "${var.project_config.project_ids.exp}${local.project_suffix}"
|
||||
)
|
||||
}
|
||||
|
|
|
@ -202,8 +202,7 @@ project_config = {
|
|||
parent = "folders/1111111111"
|
||||
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).
|
||||
|
@ -229,8 +228,7 @@ module "data-platform" {
|
|||
}
|
||||
prefix = "myprefix"
|
||||
}
|
||||
|
||||
# tftest modules=43 resources=285
|
||||
# tftest modules=43 resources=279
|
||||
```
|
||||
|
||||
## Customizations
|
||||
|
@ -262,19 +260,19 @@ You can find examples in the `[demo](./demo)` folder.
|
|||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [organization_domain](variables.tf#L159) | Organization domain. | <code>string</code> | ✓ | |
|
||||
| [prefix](variables.tf#L164) | 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({ billing_account_id = optional(string, null) parent = string project_ids = optional(object({ drop = string load = string orc = string trf = string dwh-lnd = string dwh-cur = string dwh-conf = string common = string exp = string }), { drop = "drp" load = "lod" orc = "orc" trf = "trf" dwh-lnd = "dwh-lnd" dwh-cur = "dwh-cur" dwh-conf = "dwh-conf" common = "cmn" exp = "exp" } ) })">object({…})</code> | ✓ | |
|
||||
| [composer_config](variables.tf#L17) | Cloud Composer config. | <code title="object({ disable_deployment = optional(bool) environment_size = optional(string, "ENVIRONMENT_SIZE_SMALL") software_config = optional(object({ airflow_config_overrides = optional(any) pypi_packages = optional(any) env_variables = optional(map(string)) image_version = string }), { image_version = "composer-2-airflow-2" }) workloads_config = optional(object({ scheduler = optional(object( { cpu = number memory_gb = number storage_gb = number count = number } ), { cpu = 0.5 memory_gb = 1.875 storage_gb = 1 count = 1 }) web_server = optional(object( { cpu = number memory_gb = number storage_gb = number } ), { cpu = 0.5 memory_gb = 1.875 storage_gb = 1 }) worker = optional(object( { cpu = number memory_gb = number storage_gb = number min_count = number max_count = number } ), { cpu = 0.5 memory_gb = 1.875 storage_gb = 1 min_count = 1 max_count = 3 }) })) })">object({…})</code> | | <code title="{ environment_size = "ENVIRONMENT_SIZE_SMALL" software_config = { image_version = "composer-2-airflow-2" } workloads_config = { scheduler = { cpu = 0.5 memory_gb = 1.875 storage_gb = 1 count = 1 } web_server = { cpu = 0.5 memory_gb = 1.875 storage_gb = 1 } worker = { cpu = 0.5 memory_gb = 1.875 storage_gb = 1 min_count = 1 max_count = 3 } } }">{…}</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(object({ description = optional(string) iam = optional(map(list(string)), {}) }))">map(object({…}))</code> | | <code title="{ "3_Confidential" = {} "2_Private" = {} "1_Sensitive" = {} }">{…}</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> |
|
||||
| [groups](variables.tf#L120) | User groups. | <code>map(string)</code> | | <code title="{ data-analysts = "gcp-data-analysts" data-engineers = "gcp-data-engineers" data-security = "gcp-data-security" }">{…}</code> |
|
||||
| [location](variables.tf#L130) | Location used for multi-regional resources. | <code>string</code> | | <code>"eu"</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({ host_project = string network_self_link = string subnet_self_links = object({ load = string transformation = string orchestration = string }) composer_ip_ranges = object({ cloudsql = string gke_master = string }) composer_secondary_ranges = object({ pods = string services = string }) })">object({…})</code> | | <code>null</code> |
|
||||
| [project_services](variables.tf#L207) | List of core services enabled on all projects. | <code>list(string)</code> | | <code title="[ "cloudresourcemanager.googleapis.com", "iam.googleapis.com", "serviceusage.googleapis.com", "stackdriver.googleapis.com" ]">[…]</code> |
|
||||
| [project_suffix](variables.tf#L218) | 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>"europe-west1"</code> |
|
||||
| [service_encryption_keys](variables.tf#L230) | Cloud KMS to use to encrypt different services. Key location should match service region. | <code title="object({ bq = string composer = string dataflow = string storage = string pubsub = string })">object({…})</code> | | <code>null</code> |
|
||||
| [organization_domain](variables.tf#L164) | Organization domain. | <code>string</code> | ✓ | |
|
||||
| [prefix](variables.tf#L169) | Prefix used for resource names. | <code>string</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({ billing_account_id = optional(string, null) parent = string project_ids = optional(object({ drop = string load = string orc = string trf = string dwh-lnd = string dwh-cur = string dwh-conf = string common = string exp = string }), { drop = "drp" load = "lod" orc = "orc" trf = "trf" dwh-lnd = "dwh-lnd" dwh-cur = "dwh-cur" dwh-conf = "dwh-conf" common = "cmn" exp = "exp" } ) })">object({…})</code> | ✓ | |
|
||||
| [composer_config](variables.tf#L17) | Cloud Composer config. | <code title="object({ disable_deployment = optional(bool) environment_size = optional(string, "ENVIRONMENT_SIZE_SMALL") software_config = optional( object({ airflow_config_overrides = optional(any) pypi_packages = optional(any) env_variables = optional(map(string)) image_version = string }), { image_version = "composer-2-airflow-2" } ) workloads_config = optional( object({ scheduler = optional( object({ cpu = number memory_gb = number storage_gb = number count = number }), { cpu = 0.5 memory_gb = 1.875 storage_gb = 1 count = 1 } ) web_server = optional( object({ cpu = number memory_gb = number storage_gb = number }), { cpu = 0.5 memory_gb = 1.875 storage_gb = 1 } ) worker = optional( object({ cpu = number memory_gb = number storage_gb = number min_count = number max_count = number }), { cpu = 0.5 memory_gb = 1.875 storage_gb = 1 min_count = 1 max_count = 3 } ) })) })">object({…})</code> | | <code title="{ environment_size = "ENVIRONMENT_SIZE_SMALL" software_config = { image_version = "composer-2-airflow-2" } workloads_config = { scheduler = { cpu = 0.5 memory_gb = 1.875 storage_gb = 1 count = 1 } web_server = { cpu = 0.5 memory_gb = 1.875 storage_gb = 1 } worker = { cpu = 0.5 memory_gb = 1.875 storage_gb = 1 min_count = 1 max_count = 3 } } }">{…}</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(object({ description = optional(string) iam = optional(map(list(string)), {}) }))">map(object({…}))</code> | | <code title="{ "3_Confidential" = {} "2_Private" = {} "1_Sensitive" = {} }">{…}</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#L125) | User groups. | <code>map(string)</code> | | <code title="{ data-analysts = "gcp-data-analysts" data-engineers = "gcp-data-engineers" data-security = "gcp-data-security" }">{…}</code> |
|
||||
| [location](variables.tf#L135) | Location used for multi-regional resources. | <code>string</code> | | <code>"eu"</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({ host_project = string network_self_link = string subnet_self_links = object({ load = string transformation = string orchestration = string }) composer_ip_ranges = object({ cloudsql = string gke_master = string }) composer_secondary_ranges = object({ pods = string services = string }) })">object({…})</code> | | <code>null</code> |
|
||||
| [project_services](variables.tf#L212) | List of core services enabled on all projects. | <code>list(string)</code> | | <code title="[ "cloudresourcemanager.googleapis.com", "iam.googleapis.com", "serviceusage.googleapis.com", "stackdriver.googleapis.com" ]">[…]</code> |
|
||||
| [project_suffix](variables.tf#L223) | Suffix used only for project ids. | <code>string</code> | | <code>null</code> |
|
||||
| [region](variables.tf#L229) | Region used for regional resources. | <code>string</code> | | <code>"europe-west1"</code> |
|
||||
| [service_encryption_keys](variables.tf#L235) | Cloud KMS to use to encrypt different services. Key location should match service region. | <code title="object({ bq = string composer = string dataflow = string storage = string pubsub = string })">object({…})</code> | | <code>null</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
@ -296,18 +294,3 @@ Features to add in future releases:
|
|||
- Add example on how to use Cloud Data Loss Prevention
|
||||
- Add solution to handle Tables, Views, and Authorized Views 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
|
||||
```
|
||||
|
|
|
@ -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]...
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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]...
|
||||
}
|
||||
}
|
|
@ -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]...
|
||||
}
|
||||
}
|
|
@ -35,6 +35,22 @@ locals {
|
|||
groups_iam = {
|
||||
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}"
|
||||
service_encryption_keys = var.service_encryption_keys
|
||||
shared_vpc_project = try(var.network_config.host_project, null)
|
||||
|
@ -57,6 +73,7 @@ locals {
|
|||
]
|
||||
]) : "${binding.role}-${binding.member}" => binding
|
||||
}
|
||||
use_projects = var.project_config.billing_account_id == null
|
||||
use_shared_vpc = var.network_config != null
|
||||
}
|
||||
|
||||
|
|
|
@ -19,54 +19,59 @@ variable "composer_config" {
|
|||
type = object({
|
||||
disable_deployment = optional(bool)
|
||||
environment_size = optional(string, "ENVIRONMENT_SIZE_SMALL")
|
||||
software_config = optional(object({
|
||||
airflow_config_overrides = optional(any)
|
||||
pypi_packages = optional(any)
|
||||
env_variables = optional(map(string))
|
||||
image_version = string
|
||||
}), {
|
||||
image_version = "composer-2-airflow-2"
|
||||
})
|
||||
workloads_config = optional(object({
|
||||
scheduler = optional(object(
|
||||
{
|
||||
cpu = number
|
||||
memory_gb = number
|
||||
storage_gb = number
|
||||
count = number
|
||||
}
|
||||
), {
|
||||
cpu = 0.5
|
||||
memory_gb = 1.875
|
||||
storage_gb = 1
|
||||
count = 1
|
||||
})
|
||||
web_server = optional(object(
|
||||
{
|
||||
cpu = number
|
||||
memory_gb = number
|
||||
storage_gb = number
|
||||
}
|
||||
), {
|
||||
cpu = 0.5
|
||||
memory_gb = 1.875
|
||||
storage_gb = 1
|
||||
})
|
||||
worker = optional(object(
|
||||
{
|
||||
cpu = number
|
||||
memory_gb = number
|
||||
storage_gb = number
|
||||
min_count = number
|
||||
max_count = number
|
||||
}
|
||||
), {
|
||||
cpu = 0.5
|
||||
memory_gb = 1.875
|
||||
storage_gb = 1
|
||||
min_count = 1
|
||||
max_count = 3
|
||||
})
|
||||
software_config = optional(
|
||||
object({
|
||||
airflow_config_overrides = optional(any)
|
||||
pypi_packages = optional(any)
|
||||
env_variables = optional(map(string))
|
||||
image_version = string
|
||||
}),
|
||||
{ image_version = "composer-2-airflow-2" }
|
||||
)
|
||||
workloads_config = optional(
|
||||
object({
|
||||
scheduler = optional(
|
||||
object({
|
||||
cpu = number
|
||||
memory_gb = number
|
||||
storage_gb = number
|
||||
count = number
|
||||
}),
|
||||
{
|
||||
cpu = 0.5
|
||||
memory_gb = 1.875
|
||||
storage_gb = 1
|
||||
count = 1
|
||||
}
|
||||
)
|
||||
web_server = optional(
|
||||
object({
|
||||
cpu = number
|
||||
memory_gb = number
|
||||
storage_gb = number
|
||||
}),
|
||||
{
|
||||
cpu = 0.5
|
||||
memory_gb = 1.875
|
||||
storage_gb = 1
|
||||
}
|
||||
)
|
||||
worker = optional(
|
||||
object({
|
||||
cpu = number
|
||||
memory_gb = number
|
||||
storage_gb = number
|
||||
min_count = number
|
||||
max_count = number
|
||||
}),
|
||||
{
|
||||
cpu = 0.5
|
||||
memory_gb = 1.875
|
||||
storage_gb = 1
|
||||
min_count = 1
|
||||
max_count = 3
|
||||
}
|
||||
)
|
||||
}))
|
||||
})
|
||||
default = {
|
||||
|
|
|
@ -16,9 +16,26 @@
|
|||
|
||||
locals {
|
||||
iam_lnd = {
|
||||
"roles/storage.objectCreator" = [module.land-sa-0.iam_email]
|
||||
"roles/storage.objectViewer" = [module.processing-sa-cmp-0.iam_email]
|
||||
"roles/storage.objectAdmin" = [module.processing-sa-0.iam_email]
|
||||
"roles/storage.objectCreator" = [
|
||||
module.land-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
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
prefix = (
|
||||
var.project_config.billing_account_id == null ? null : var.prefix
|
||||
)
|
||||
name = (
|
||||
var.project_config.billing_account_id == null
|
||||
? var.project_config.project_ids.landing
|
||||
: "${var.project_config.project_ids.landing}${local.project_suffix}"
|
||||
)
|
||||
iam = var.project_config.billing_account_id != null ? local.iam_lnd : null
|
||||
iam_additive = var.project_config.billing_account_id == null ? local.iam_lnd : null
|
||||
iam = (
|
||||
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 = [
|
||||
"bigquery.googleapis.com",
|
||||
"bigqueryreservation.googleapis.com",
|
||||
|
|
|
@ -15,15 +15,23 @@
|
|||
# tfdoc:file:description Processing project and VPC.
|
||||
|
||||
locals {
|
||||
iam_processing = {
|
||||
iam_prc = {
|
||||
"roles/bigquery.jobUser" = [
|
||||
module.processing-sa-cmp-0.iam_email,
|
||||
module.processing-sa-0.iam_email
|
||||
]
|
||||
"roles/composer.admin" = [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.admin" = [
|
||||
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" = [
|
||||
"serviceAccount:${module.processing-project.service_accounts.robots.composer}"
|
||||
]
|
||||
|
@ -37,20 +45,39 @@ locals {
|
|||
module.processing-sa-0.iam_email
|
||||
]
|
||||
"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" = [
|
||||
module.processing-sa-cmp-0.iam_email,
|
||||
"serviceAccount:${module.processing-project.service_accounts.robots.composer}",
|
||||
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 = (
|
||||
local.use_shared_vpc
|
||||
? 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 = (
|
||||
local.use_shared_vpc
|
||||
|
@ -64,15 +91,23 @@ module "processing-project" {
|
|||
parent = var.project_config.parent
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
prefix = (
|
||||
var.project_config.billing_account_id == null ? null : var.prefix
|
||||
)
|
||||
name = (
|
||||
var.project_config.billing_account_id == null
|
||||
? var.project_config.project_ids.processing
|
||||
: "${var.project_config.project_ids.processing}${local.project_suffix}"
|
||||
)
|
||||
iam = var.project_config.billing_account_id != null ? local.iam_processing : null
|
||||
iam_additive = var.project_config.billing_account_id == null ? local.iam_processing : null
|
||||
oslogin = false
|
||||
iam = (
|
||||
var.project_config.billing_account_id == null ? {} : local.iam_prc
|
||||
)
|
||||
iam_bindings_additive = (
|
||||
var.project_config.billing_account_id != null ? {} : local.iam_prc_additive
|
||||
)
|
||||
compute_metadata = {
|
||||
enable-oslogin = "false"
|
||||
}
|
||||
services = [
|
||||
"bigquery.googleapis.com",
|
||||
"bigqueryreservation.googleapis.com",
|
||||
|
|
|
@ -15,15 +15,32 @@
|
|||
# tfdoc:file:description Data curated project and resources.
|
||||
|
||||
locals {
|
||||
cur_iam = {
|
||||
"roles/bigquery.dataOwner" = [module.processing-sa-0.iam_email]
|
||||
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"
|
||||
]
|
||||
iam_cur = {
|
||||
"roles/bigquery.dataOwner" = [
|
||||
module.processing-sa-0.iam_email
|
||||
]
|
||||
"roles/bigquery.dataViewer" = [
|
||||
module.cur-sa-0.iam_email,
|
||||
local.groups_iam.data-analysts,
|
||||
local.groups_iam.data-engineers
|
||||
]
|
||||
"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,
|
||||
local.groups_iam.data-analysts,
|
||||
local.groups_iam.data-engineers
|
||||
|
@ -43,22 +60,21 @@ locals {
|
|||
local.groups_iam.data-analysts,
|
||||
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
|
||||
|
@ -68,15 +84,21 @@ module "cur-project" {
|
|||
parent = var.project_config.parent
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
prefix = (
|
||||
var.project_config.billing_account_id == null ? null : var.prefix
|
||||
)
|
||||
name = (
|
||||
var.project_config.billing_account_id == null
|
||||
? var.project_config.project_ids.curated
|
||||
: "${var.project_config.project_ids.curated}${local.project_suffix}"
|
||||
)
|
||||
iam = var.project_config.billing_account_id != null ? local.cur_iam : {}
|
||||
iam_additive = var.project_config.billing_account_id == null ? local.cur_iam : {}
|
||||
services = local.cur_services
|
||||
iam = (
|
||||
var.project_config.billing_account_id != null ? {} : local.iam_cur
|
||||
)
|
||||
iam_bindings_additive = (
|
||||
var.project_config.billing_account_id == null ? {} : local.iam_cur_additive
|
||||
)
|
||||
services = local.cur_services
|
||||
service_encryption_key_ids = {
|
||||
bq = [var.service_encryption_keys.bq]
|
||||
storage = [var.service_encryption_keys.storage]
|
||||
|
|
|
@ -15,15 +15,23 @@
|
|||
# tfdoc:file:description Common project and resources.
|
||||
|
||||
locals {
|
||||
iam_common = {
|
||||
"roles/dlp.admin" = [local.groups_iam.data-security]
|
||||
"roles/dlp.estimatesAdmin" = [local.groups_iam.data-engineers]
|
||||
"roles/dlp.reader" = [local.groups_iam.data-engineers]
|
||||
iam_cmn = {
|
||||
"roles/dlp.admin" = [
|
||||
local.groups_iam.data-security
|
||||
]
|
||||
"roles/dlp.estimatesAdmin" = [
|
||||
local.groups_iam.data-engineers
|
||||
]
|
||||
"roles/dlp.reader" = [
|
||||
local.groups_iam.data-engineers
|
||||
]
|
||||
"roles/dlp.user" = [
|
||||
module.processing-sa-0.iam_email,
|
||||
local.groups_iam.data-engineers
|
||||
]
|
||||
"roles/datacatalog.admin" = [local.groups_iam.data-security]
|
||||
"roles/datacatalog.admin" = [
|
||||
local.groups_iam.data-security
|
||||
]
|
||||
"roles/datacatalog.viewer" = [
|
||||
module.processing-sa-0.iam_email,
|
||||
local.groups_iam.data-analysts
|
||||
|
@ -32,20 +40,37 @@ locals {
|
|||
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" {
|
||||
source = "../../../modules/project"
|
||||
parent = var.project_config.parent
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
prefix = (
|
||||
var.project_config.billing_account_id == null ? 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}"
|
||||
)
|
||||
iam = var.project_config.billing_account_id != null ? local.iam_common : null
|
||||
iam_additive = var.project_config.billing_account_id == null ? local.iam_common : null
|
||||
iam = (
|
||||
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 = [
|
||||
"cloudresourcemanager.googleapis.com",
|
||||
"datacatalog.googleapis.com",
|
||||
|
|
|
@ -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.
|
||||
|
||||
<!-- 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
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
# tftest modules=23 resources=123
|
||||
# tftest modules=23 resources=135
|
||||
```
|
||||
|
||||
## Customizations
|
||||
|
|
|
@ -24,7 +24,7 @@ If the network_config variable is not provided, one VPC will be created in each
|
|||
|
||||
## 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.
|
||||
- owner on the project in case you use an existing project.
|
||||
|
|
|
@ -14,16 +14,6 @@
|
|||
|
||||
locals {
|
||||
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.dataOwner" = [
|
||||
module.service-account-df.iam_email
|
||||
|
@ -34,9 +24,7 @@ locals {
|
|||
"roles/bigquery.jobUser" = [
|
||||
module.service-account-bq.iam_email
|
||||
]
|
||||
# Compute
|
||||
"roles/compute.viewer" = var.data_eng_principals
|
||||
# Dataflow roles
|
||||
"roles/dataflow.admin" = concat(
|
||||
[module.service-account-orch.iam_email],
|
||||
var.data_eng_principals
|
||||
|
@ -45,6 +33,25 @@ locals {
|
|||
"roles/dataflow.worker" = [
|
||||
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(
|
||||
module.vpc[0].subnets["${var.region}/subnet"].self_link,
|
||||
|
@ -75,8 +82,12 @@ module "project" {
|
|||
"storage.googleapis.com",
|
||||
"storage-component.googleapis.com",
|
||||
]
|
||||
iam = var.project_config.billing_account_id != null ? local.iam : {}
|
||||
iam_additive = var.project_config.billing_account_id == null ? local.iam : {}
|
||||
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 : {
|
||||
attach = true
|
||||
host_project = var.network_config.host_project
|
||||
|
|
|
@ -153,25 +153,24 @@ terraform init
|
|||
terraform apply
|
||||
```
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Variables
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [access_policy_config](variables.tf#L17) | Provide 'access_policy_create' values if a folder scoped Access Policy creation is needed, uses existing 'policy_name' otherwise. Parent is in 'organizations/123456' format. Policy will be created scoped to the folder. | <code title="object({ policy_name = optional(string, null) access_policy_create = optional(object({ parent = string title = string }), null) })">object({…})</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({ folder_id = optional(string, null) folder_create = optional(object({ display_name = string parent = string }), null) })">object({…})</code> | ✓ | |
|
||||
| [organization](variables.tf#L128) | Organization details. | <code title="object({ domain = string id = string })">object({…})</code> | ✓ | |
|
||||
| [prefix](variables.tf#L136) | 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({ billing_account_id = optional(string, null) project_ids = optional(object({ sec-core = string audit-logs = string }), { sec-core = "sec-core" audit-logs = "audit-logs" } ) })">object({…})</code> | ✓ | |
|
||||
| [organization](variables.tf#L129) | Organization details. | <code title="object({ domain = string id = string })">object({…})</code> | ✓ | |
|
||||
| [prefix](variables.tf#L137) | Prefix used for resources that need unique names. | <code>string</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({ billing_account_id = optional(string, null) project_ids = optional(object({ sec-core = string audit-logs = string }), { sec-core = "sec-core" audit-logs = "audit-logs" } ) })">object({…})</code> | ✓ | |
|
||||
| [data_dir](variables.tf#L29) | Relative path for the folder storing configuration data. | <code>string</code> | | <code>"data"</code> |
|
||||
| [enable_features](variables.tf#L35) | Flag to enable features on the solution. | <code title="object({ encryption = optional(bool, false) log_sink = optional(bool, true) vpc_sc = optional(bool, true) })">object({…})</code> | | <code title="{ encryption = false log_sink = true vpc_sc = true }">{…}</code> |
|
||||
| [groups](variables.tf#L65) | User groups. | <code title="object({ workload-engineers = optional(string, "gcp-data-engineers") workload-security = optional(string, "gcp-data-security") })">object({…})</code> | | <code>{}</code> |
|
||||
| [kms_keys](variables.tf#L75) | KMS keys to create, keyed by name. | <code title="map(object({ iam = optional(map(list(string)), {}) labels = optional(map(string), {}) locations = optional(list(string), ["global", "europe", "europe-west1"]) rotation_period = optional(string, "7776000s") }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [log_locations](variables.tf#L86) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object({ bq = optional(string, "europe") storage = optional(string, "europe") logging = optional(string, "global") pubsub = optional(string, "global") })">object({…})</code> | | <code title="{ bq = "europe" storage = "europe" logging = "global" pubsub = null }">{…}</code> |
|
||||
| [log_sinks](variables.tf#L103) | Org-level log sinks, in name => {type, filter} format. | <code title="map(object({ filter = string type = string }))">map(object({…}))</code> | | <code title="{ audit-logs = { filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\"" type = "bigquery" } vpc-sc = { filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\"" type = "bigquery" } }">{…}</code> |
|
||||
| [vpc_sc_access_levels](variables.tf#L161) | VPC SC access level definitions. | <code title="map(object({ combining_function = optional(string) conditions = optional(list(object({ device_policy = optional(object({ allowed_device_management_levels = optional(list(string)) allowed_encryption_statuses = optional(list(string)) require_admin_approval = bool require_corp_owned = bool require_screen_lock = optional(bool) os_constraints = optional(list(object({ os_type = string minimum_version = optional(string) require_verified_chrome_os = optional(bool) }))) })) ip_subnetworks = optional(list(string), []) members = optional(list(string), []) negate = optional(bool) regions = optional(list(string), []) required_access_levels = optional(list(string), []) })), []) description = optional(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [vpc_sc_egress_policies](variables.tf#L190) | VPC SC egress policy definitions. | <code title="map(object({ from = object({ identity_type = optional(string, "ANY_IDENTITY") identities = optional(list(string)) }) to = object({ operations = optional(list(object({ method_selectors = optional(list(string)) service_name = string })), []) resources = optional(list(string)) resource_type_external = optional(bool, false) }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [vpc_sc_ingress_policies](variables.tf#L210) | VPC SC ingress policy definitions. | <code title="map(object({ from = object({ access_levels = optional(list(string), []) identity_type = optional(string) identities = optional(list(string)) resources = optional(list(string), []) }) to = object({ operations = optional(list(object({ method_selectors = optional(list(string)) service_name = string })), []) resources = optional(list(string)) }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [kms_keys](variables.tf#L75) | KMS keys to create, keyed by name. | <code title="map(object({ iam = optional(map(list(string)), {}) iam_bindings_additive = optional(map(map(any)), {}) labels = optional(map(string), {}) locations = optional(list(string), ["global", "europe", "europe-west1"]) rotation_period = optional(string, "7776000s") }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [log_locations](variables.tf#L87) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object({ bq = optional(string, "europe") storage = optional(string, "europe") logging = optional(string, "global") pubsub = optional(string, "global") })">object({…})</code> | | <code title="{ bq = "europe" storage = "europe" logging = "global" pubsub = null }">{…}</code> |
|
||||
| [log_sinks](variables.tf#L104) | Org-level log sinks, in name => {type, filter} format. | <code title="map(object({ filter = string type = string }))">map(object({…}))</code> | | <code title="{ audit-logs = { filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\"" type = "bigquery" } vpc-sc = { filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\"" type = "bigquery" } }">{…}</code> |
|
||||
| [vpc_sc_access_levels](variables.tf#L162) | VPC SC access level definitions. | <code title="map(object({ combining_function = optional(string) conditions = optional(list(object({ device_policy = optional(object({ allowed_device_management_levels = optional(list(string)) allowed_encryption_statuses = optional(list(string)) require_admin_approval = bool require_corp_owned = bool require_screen_lock = optional(bool) os_constraints = optional(list(object({ os_type = string minimum_version = optional(string) require_verified_chrome_os = optional(bool) }))) })) ip_subnetworks = optional(list(string), []) members = optional(list(string), []) negate = optional(bool) regions = optional(list(string), []) required_access_levels = optional(list(string), []) })), []) description = optional(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [vpc_sc_egress_policies](variables.tf#L191) | VPC SC egress policy definitions. | <code title="map(object({ from = object({ identity_type = optional(string, "ANY_IDENTITY") identities = optional(list(string)) }) to = object({ operations = optional(list(object({ method_selectors = optional(list(string)) service_name = string })), []) resources = optional(list(string)) resource_type_external = optional(bool, false) }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [vpc_sc_ingress_policies](variables.tf#L211) | VPC SC ingress policy definitions. | <code title="map(object({ from = object({ access_levels = optional(list(string), []) identity_type = optional(string) identities = optional(list(string)) resources = optional(list(string), []) }) to = object({ operations = optional(list(object({ method_selectors = optional(list(string)) service_name = string })), []) resources = optional(list(string)) }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
@ -180,7 +179,6 @@ terraform apply
|
|||
| [folders](outputs.tf#L15) | Folders id. | |
|
||||
| [folders_sink_writer_identities](outputs.tf#L23) | Folders id. | |
|
||||
| [kms_keys](outputs.tf#L31) | Cloud KMS encryption keys created. | |
|
||||
|
||||
<!-- END TFDOC -->
|
||||
## Test
|
||||
|
||||
|
|
|
@ -25,12 +25,9 @@ locals {
|
|||
for k, v in var.kms_keys : k => v if contains(v.locations, loc)
|
||||
}
|
||||
}
|
||||
|
||||
kms_log_locations = distinct(flatten([
|
||||
for k, v in local.kms_log_sink_keys : compact(v.locations)
|
||||
]))
|
||||
|
||||
# Log sink keys
|
||||
kms_log_sink_keys = {
|
||||
"storage" = {
|
||||
labels = {}
|
||||
|
@ -61,8 +58,12 @@ module "sec-project" {
|
|||
name = var.project_config.project_ids["sec-core"]
|
||||
parent = module.folder.id
|
||||
billing_account = var.project_config.billing_account_id
|
||||
project_create = var.project_config.billing_account_id != null && var.enable_features.encryption
|
||||
prefix = var.project_config.billing_account_id == null ? null : var.prefix
|
||||
project_create = (
|
||||
var.project_config.billing_account_id != null && var.enable_features.encryption
|
||||
)
|
||||
prefix = (
|
||||
var.project_config.billing_account_id == null ? null : var.prefix
|
||||
)
|
||||
group_iam = {
|
||||
(local.groups.workload-security) = [
|
||||
"roles/editor"
|
||||
|
@ -76,17 +77,23 @@ module "sec-project" {
|
|||
}
|
||||
|
||||
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"
|
||||
project_id = module.sec-project[0].project_id
|
||||
keyring = {
|
||||
location = each.key
|
||||
name = "sec-${each.key}"
|
||||
}
|
||||
# rename to `key_iam` to switch to authoritative bindings
|
||||
key_iam_additive = {
|
||||
key_iam = {
|
||||
for k, v in local.kms_locations_keys[each.key] : k => v.iam
|
||||
}
|
||||
key_iam_bindings_additive = {
|
||||
for k, v in local.kms_locations_keys[each.key] : k => v.iam_bindings_additive
|
||||
}
|
||||
keys = local.kms_locations_keys[each.key]
|
||||
}
|
||||
|
||||
|
|
|
@ -75,10 +75,11 @@ variable "groups" {
|
|||
variable "kms_keys" {
|
||||
description = "KMS keys to create, keyed by name."
|
||||
type = map(object({
|
||||
iam = optional(map(list(string)), {})
|
||||
labels = optional(map(string), {})
|
||||
locations = optional(list(string), ["global", "europe", "europe-west1"])
|
||||
rotation_period = optional(string, "7776000s")
|
||||
iam = optional(map(list(string)), {})
|
||||
iam_bindings_additive = optional(map(map(any)), {})
|
||||
labels = optional(map(string), {})
|
||||
locations = optional(list(string), ["global", "europe", "europe-west1"])
|
||||
rotation_period = optional(string, "7776000s")
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
|
|
@ -73,9 +73,6 @@ module "project" {
|
|||
"compute.googleapis.com",
|
||||
"secretmanager.googleapis.com",
|
||||
]
|
||||
|
||||
iam = {}
|
||||
iam_additive = {}
|
||||
shared_vpc_service_config = var.shared_vpc_project_id == null ? null : {
|
||||
attach = true
|
||||
host_project = var.shared_vpc_project_id
|
||||
|
|
|
@ -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
|
||||
* `"compute.organizations.enableXpnResource"`,
|
||||
* `"compute.organizations.disableXpnResource"`,
|
||||
* `"compute.subnetworks.setIamPolicy"`,
|
||||
* `"dns.networks.bindPrivateDNSZone"`
|
||||
* and role `"roles/orgpolicy.policyAdmin"`
|
||||
* **on each folder** where projects will be created
|
||||
* `"roles/logging.admin"`
|
||||
* `"roles/owner"`
|
||||
* `"roles/resourcemanager.folderAdmin"`
|
||||
* `"roles/resourcemanager.projectCreator"`
|
||||
* **on the host project** for the Shared VPC/s
|
||||
* `"roles/browser"`
|
||||
* `"roles/compute.viewer"`
|
||||
* `"roles/dns.admin"`
|
||||
The code is meant to be executed by a high level service accounts with powerful permissions:
|
||||
|
||||
- Shared VPC connection if service project attachment is desired
|
||||
- project creation on the nodes (folder or org) where projects will be defined
|
||||
|
||||
The module also supports optional creation of specific resources that usually part of the project creation flow:
|
||||
|
||||
- service accounts used for VM instances, and associated basic roles
|
||||
- KMS key encrypt/decrypt permissions for service identities in the project
|
||||
- membership in VPC SC standard or bridge perimeters
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
locals {
|
||||
defaults = yamldecode(file(local._defaults_file))
|
||||
projects = {
|
||||
for f in fileset("${local._data_dir}", "**/*.yaml") :
|
||||
trimsuffix(f, ".yaml") => yamldecode(file("${local._data_dir}/${f}"))
|
||||
module "project-factory" {
|
||||
source = "./fabric/blueprints/factories/project-factory"
|
||||
data_defaults = {
|
||||
billing_account = "012345-67890A-ABCDEF"
|
||||
}
|
||||
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"
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
# tftest modules=6 resources=12 files=prj-app-1,prj-app-2 inventory=example.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
# ./data/projects/project-example-one.yaml
|
||||
# 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
|
||||
billing_account: 012345-67890A-BCDEF0
|
||||
labels:
|
||||
environment: prod
|
||||
|
||||
# [opt] Org policy overrides defined at project level
|
||||
org_policies:
|
||||
compute.disableGuestAttributesAccess:
|
||||
rules:
|
||||
- enforce: true
|
||||
compute.trustedImageProjects:
|
||||
rules:
|
||||
- allow:
|
||||
values:
|
||||
- projects/fast-dev-iac-core-0
|
||||
compute.vmExternalIpAccess:
|
||||
rules:
|
||||
- deny:
|
||||
all: true
|
||||
|
||||
# [opt] Service account to create for the project and their roles on the project
|
||||
# in name => [roles] format
|
||||
service_accounts:
|
||||
another-service-account:
|
||||
- roles/compute.admin
|
||||
my-service-account:
|
||||
- roles/compute.admin
|
||||
|
||||
# [opt] 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.
|
||||
app: app-1
|
||||
team: foo
|
||||
service_encryption_key_ids:
|
||||
compute:
|
||||
- projects/kms-central-prj/locations/europe-west3/keyRings/my-keyring/cryptoKeys/europe3-gce
|
||||
services:
|
||||
- storage.googleapis.com
|
||||
- stackdriver.googleapis.com
|
||||
- compute.googleapis.com
|
||||
- storage.googleapis.com
|
||||
service_accounts:
|
||||
app-1-be: {}
|
||||
app-1-fe: {}
|
||||
|
||||
# [opt] Roles to assign to the robots service accounts in robot => [roles] format
|
||||
services_iam:
|
||||
compute:
|
||||
- roles/storage.objectViewer
|
||||
# tftest-file id=prj-app-1 path=data/prj-app-1.yaml
|
||||
```
|
||||
|
||||
# [opt] VPC setup.
|
||||
# If set enables the `compute.googleapis.com` service and configures
|
||||
# service project attachment
|
||||
vpc:
|
||||
```yaml
|
||||
labels:
|
||||
app: app-1
|
||||
team: foo
|
||||
service_accounts:
|
||||
app-2-be: {}
|
||||
|
||||
# [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-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
|
||||
# tftest-file id=prj-app-2 path=data/prj-app-2.yaml
|
||||
```
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Variables
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [billing_account_id](variables.tf#L17) | Billing account id. | <code>string</code> | ✓ | |
|
||||
| [prefix](variables.tf#L144) | Prefix used for resource names. | <code>string</code> | ✓ | |
|
||||
| [project_id](variables.tf#L153) | Project id. | <code>string</code> | ✓ | |
|
||||
| [billing_alert](variables.tf#L22) | Billing alert configuration. | <code title="object({ amount = number thresholds = object({ current = list(number) forecasted = list(number) }) credit_treatment = string })">object({…})</code> | | <code>null</code> |
|
||||
| [defaults](variables.tf#L35) | Project factory default values. | <code title="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 })">object({…})</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(string)</code> | | <code>[]</code> |
|
||||
| [essential_contacts](variables.tf#L69) | Email contacts to be used for billing and GCP notifications. | <code>list(string)</code> | | <code>[]</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(list(string))</code> | | <code>{}</code> |
|
||||
| [group_iam_additive](variables.tf#L87) | Custom additive IAM settings in group => [role] format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam](variables.tf#L93) | Custom IAM settings in role => [principal] format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_additive](variables.tf#L99) | Custom additive IAM settings in role => [principal] format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [kms_service_agents](variables.tf#L105) | KMS IAM configuration in as service => [key]. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [labels](variables.tf#L111) | Labels to be assigned at project level. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [org_policies](variables.tf#L117) | Org-policy overrides at project level. | <code title="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) }), {}) })), []) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [service_accounts](variables.tf#L158) | Service accounts to be created, and roles assigned them on the project. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [service_accounts_additive](variables.tf#L164) | Service accounts to be created, and roles assigned them on the project additively. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [service_accounts_iam](variables.tf#L170) | IAM bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}. | <code>map(map(list(string)))</code> | | <code>{}</code> |
|
||||
| [service_accounts_iam_additive](variables.tf#L177) | IAM additive bindings on service account resources. Format is KEY => {ROLE => [MEMBERS]}. | <code>map(map(list(string)))</code> | | <code>{}</code> |
|
||||
| [service_identities_iam](variables.tf#L184) | Custom IAM settings for service identities in service => [role] format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [service_identities_iam_additive](variables.tf#L191) | Custom additive IAM settings for service identities in service => [role] format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [services](variables.tf#L198) | Services to be enabled for the project. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [vpc](variables.tf#L205) | VPC configuration for the project. | <code title="object({ host_project = string gke_setup = optional(object({ enable_security_admin = optional(bool, false) enable_host_service_agent = optional(bool, false) }), {}) service_iam_grants = optional(list(string), []) service_identity_iam = optional(map(list(string)), {}) subnets_iam = optional(map(list(string)), {}) })">object({…})</code> | | <code title="{ host_project = null }">{…}</code> |
|
||||
| [factory_data](variables.tf#L83) | Project data from either YAML files or externally parsed data. | <code title="object({ data = optional(map(any)) data_path = optional(string) })">object({…})</code> | ✓ | |
|
||||
| [data_defaults](variables.tf#L17) | Optional default values used when corresponding project data from files are missing. | <code title="object({ billing_account = optional(string) contacts = optional(map(list(string)), {}) labels = optional(map(string), {}) metric_scopes = optional(list(string), []) prefix = optional(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), {}) service_accounts = optional(map(object({ default_roles = optional(bool, true) })), {}) })">object({…})</code> | | <code>{}</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({ contacts = optional(map(list(string)), {}) labels = optional(map(string), {}) metric_scopes = optional(list(string), []) service_encryption_key_ids = optional(map(list(string)), {}) service_perimeter_bridges = optional(list(string), []) services = optional(list(string), []) tag_bindings = optional(map(string), {}) service_accounts = optional(map(object({ default_roles = optional(bool, true) })), {}) })">object({…})</code> | | <code>{}</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({ 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)) service_accounts = optional(map(object({ default_roles = optional(bool, true) }))) })">object({…})</code> | | <code>{}</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
| name | description | sensitive |
|
||||
|---|---|:---:|
|
||||
| [project](outputs.tf#L19) | The project resource as return by the `project` module. | |
|
||||
| [project_id](outputs.tf#L29) | Project ID. | |
|
||||
|
||||
| [projects](outputs.tf#L17) | Project module outputs. | |
|
||||
| [service_accounts](outputs.tf#L22) | Service account emails. | |
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
])
|
||||
}
|
|
@ -14,222 +14,68 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
locals {
|
||||
_gke_config_service_identity_iam = {
|
||||
"roles/compute.networkUser" = compact([
|
||||
var.vpc.gke_setup.enable_host_service_agent ? "container-engine" : null,
|
||||
local.vpc_cloudservices ? "cloudservices" : null
|
||||
])
|
||||
"roles/compute.securityAdmin" = compact([
|
||||
var.vpc.gke_setup.enable_security_admin ? "container-engine" : null,
|
||||
])
|
||||
"roles/container.hostServiceAgentUser" = compact([
|
||||
var.vpc.gke_setup.enable_host_service_agent ? "container-engine" : null
|
||||
])
|
||||
}
|
||||
|
||||
_group_iam = {
|
||||
for r in local._group_iam_bindings : r => [
|
||||
for k, v in var.group_iam :
|
||||
"group:${k}" if try(index(v, r), null) != null
|
||||
]
|
||||
}
|
||||
_group_iam_additive = {
|
||||
for r in local._group_iam_additive_bindings : r => [
|
||||
for k, v in var.group_iam_additive :
|
||||
"group:${k}" if try(index(v, r), null) != null
|
||||
]
|
||||
}
|
||||
_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)
|
||||
module "projects" {
|
||||
source = "../../../modules/project"
|
||||
for_each = local.projects
|
||||
billing_account = each.value.billing_account
|
||||
name = each.key
|
||||
parent = try(each.value.parent, null)
|
||||
prefix = each.value.prefix
|
||||
auto_create_network = try(each.value.auto_create_network, false)
|
||||
compute_metadata = try(each.value.compute_metadata, {})
|
||||
# TODO: concat lists for each key
|
||||
contacts = merge(
|
||||
each.value.contacts, var.data_merges.contacts
|
||||
)
|
||||
default_service_account = try(each.value.default_service_account, "keep")
|
||||
descriptive_name = try(each.value.descriptive_name, null)
|
||||
group_iam = try(each.value.group_iam, {})
|
||||
iam = try(each.value.iam, {})
|
||||
iam_bindings = try(each.value.iam_bindings, {})
|
||||
iam_bindings_additive = try(each.value.iam_bindings_additive, {})
|
||||
labels = each.value.labels
|
||||
lien_reason = try(each.value.lien_reason, null)
|
||||
logging_data_access = try(each.value.logging_data_access, {})
|
||||
logging_exclusions = try(each.value.logging_exclusions, {})
|
||||
logging_sinks = try(each.value.logging_sinks, {})
|
||||
metric_scopes = distinct(concat(
|
||||
each.value.metric_scopes, var.data_merges.metric_scopes
|
||||
))
|
||||
_service_accounts_iam_additive = {
|
||||
for r in local._service_accounts_iam_additive_bindings : r => [
|
||||
for k, v in var.service_accounts_additive :
|
||||
module.service-accounts[k].iam_email
|
||||
if try(index(v, r), null) != null
|
||||
]
|
||||
}
|
||||
_service_accounts_iam_additive_bindings = distinct(flatten(
|
||||
values(var.service_accounts_additive)
|
||||
service_encryption_key_ids = merge(
|
||||
each.value.service_encryption_key_ids,
|
||||
var.data_merges.service_encryption_key_ids
|
||||
)
|
||||
service_perimeter_bridges = distinct(concat(
|
||||
each.value.service_perimeter_bridges,
|
||||
var.data_merges.service_perimeter_bridges
|
||||
))
|
||||
_services = concat([
|
||||
"billingbudgets.googleapis.com",
|
||||
"essentialcontacts.googleapis.com",
|
||||
"orgpolicy.googleapis.com",
|
||||
],
|
||||
length(var.dns_zones) > 0 ? ["dns.googleapis.com"] : [],
|
||||
try(var.vpc.gke_setup, null) != null ? ["container.googleapis.com"] : [],
|
||||
var.vpc != null ? ["compute.googleapis.com"] : [],
|
||||
service_perimeter_standard = each.value.service_perimeter_standard
|
||||
services = distinct(concat(
|
||||
each.value.services,
|
||||
var.data_merges.services
|
||||
))
|
||||
shared_vpc_service_config = each.value.shared_vpc_service_config
|
||||
tag_bindings = merge(
|
||||
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" {
|
||||
source = "../../../modules/iam-service-account"
|
||||
for_each = var.service_accounts
|
||||
name = each.key
|
||||
project_id = module.project.project_id
|
||||
iam = lookup(var.service_accounts_iam, each.key, null)
|
||||
}
|
||||
|
||||
resource "google_compute_subnetwork_iam_member" "default" {
|
||||
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}"
|
||||
region = each.value.region
|
||||
role = "roles/compute.networkUser"
|
||||
member = (
|
||||
lookup(var.service_accounts, each.value.member, null) != null
|
||||
? module.service-accounts[each.value.member].iam_email
|
||||
: each.value.member
|
||||
source = "../../../modules/iam-service-account"
|
||||
for_each = {
|
||||
for k in local.service_accounts : "${k.project}-${k.name}" => k
|
||||
}
|
||||
name = each.value.name
|
||||
project_id = module.projects[each.value.project].project_id
|
||||
iam_project_roles = (
|
||||
try(each.value.options.default_roles, null) == null
|
||||
? {}
|
||||
: {
|
||||
(module.projects[each.value.project].project_id) = [
|
||||
"roles/logging.logWriter",
|
||||
"roles/monitoring.metricWriter"
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Copyright 2022 Google LLC
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,23 +14,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
# TODO(): proper outputs
|
||||
|
||||
output "project" {
|
||||
description = "The project resource as return by the `project` module."
|
||||
value = module.project
|
||||
|
||||
depends_on = [
|
||||
google_compute_subnetwork_iam_member.default,
|
||||
module.dns
|
||||
]
|
||||
output "projects" {
|
||||
description = "Project module outputs."
|
||||
value = module.projects
|
||||
}
|
||||
|
||||
output "project_id" {
|
||||
description = "Project ID."
|
||||
value = module.project.project_id
|
||||
depends_on = [
|
||||
google_compute_subnetwork_iam_member.default,
|
||||
module.dns
|
||||
]
|
||||
output "service_accounts" {
|
||||
description = "Service account emails."
|
||||
# TODO: group by project
|
||||
value = {
|
||||
for k, v in module.service-accounts : k => v.email
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
|
@ -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
|
|
@ -14,215 +14,84 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
variable "billing_account_id" {
|
||||
description = "Billing account id."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "billing_alert" {
|
||||
description = "Billing alert configuration."
|
||||
variable "data_defaults" {
|
||||
description = "Optional default values used when corresponding project data from files are missing."
|
||||
type = object({
|
||||
amount = number
|
||||
thresholds = object({
|
||||
current = list(number)
|
||||
forecasted = list(number)
|
||||
})
|
||||
credit_treatment = string
|
||||
billing_account = optional(string)
|
||||
contacts = optional(map(list(string)), {})
|
||||
labels = optional(map(string), {})
|
||||
metric_scopes = optional(list(string), [])
|
||||
prefix = optional(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
|
||||
}
|
||||
|
||||
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)
|
||||
}), {})
|
||||
})), [])
|
||||
}))
|
||||
nullable = false
|
||||
default = {}
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "prefix" {
|
||||
description = "Prefix used for resource names."
|
||||
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."
|
||||
variable "data_merges" {
|
||||
description = "Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`."
|
||||
type = object({
|
||||
host_project = string
|
||||
gke_setup = optional(object({
|
||||
enable_security_admin = optional(bool, false)
|
||||
enable_host_service_agent = optional(bool, false)
|
||||
}), {})
|
||||
service_iam_grants = optional(list(string), [])
|
||||
service_identity_iam = optional(map(list(string)), {})
|
||||
subnets_iam = optional(map(list(string)), {})
|
||||
contacts = optional(map(list(string)), {})
|
||||
labels = optional(map(string), {})
|
||||
metric_scopes = optional(list(string), [])
|
||||
service_encryption_key_ids = optional(map(list(string)), {})
|
||||
service_perimeter_bridges = optional(list(string), [])
|
||||
services = optional(list(string), [])
|
||||
tag_bindings = optional(map(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
|
||||
validation {
|
||||
condition = var.vpc.host_project != null || (
|
||||
var.vpc.host_project == null && length(var.vpc.gke_setup) == 0 && length(var.vpc.service_iam_grants) == 0 &&
|
||||
length(var.vpc.service_identity_iam) == 0 && length(var.vpc.subnets_iam) == 0
|
||||
)
|
||||
error_message = "host_project is required if providing any additional configuration for vpc"
|
||||
condition = (
|
||||
(var.factory_data.data != null ? 1 : 0) +
|
||||
(var.factory_data.data_path != null ? 1 : 0)
|
||||
) == 1
|
||||
error_message = "One of data or data_path needs to be set."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ Once the resources have been created, do the following to verify that everything
|
|||
|
||||
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 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.
|
||||
|
|
|
@ -32,9 +32,11 @@ module "project" {
|
|||
source = "../../../modules/project"
|
||||
project_create = var.project_create != null
|
||||
billing_account = try(var.project_create.billing_account, null)
|
||||
oslogin = try(var.project_create.oslogin, false)
|
||||
parent = try(var.project_create.parent, null)
|
||||
name = var.project_id
|
||||
compute_metadata = var.project_create.oslogin != true ? {} : {
|
||||
enable-oslogin = "true"
|
||||
}
|
||||
parent = try(var.project_create.parent, null)
|
||||
name = var.project_id
|
||||
services = [
|
||||
"compute.googleapis.com",
|
||||
"container.googleapis.com"
|
||||
|
|
|
@ -41,8 +41,9 @@ module "project-svc-gce" {
|
|||
prefix = var.prefix
|
||||
name = "gce"
|
||||
services = var.project_services
|
||||
oslogin = true
|
||||
oslogin_admins = var.owners_gce
|
||||
compute_metadata = {
|
||||
enable-oslogin = "true"
|
||||
}
|
||||
shared_vpc_service_config = {
|
||||
host_project = module.project-host.project_id
|
||||
service_identity_iam = {
|
||||
|
@ -50,7 +51,8 @@ module "project-svc-gce" {
|
|||
}
|
||||
}
|
||||
iam = {
|
||||
"roles/owner" = var.owners_gce
|
||||
"roles/compute.osAdminLogin" = var.owners_gce
|
||||
"roles/owner" = var.owners_gce
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,13 @@ This architecture can be used for the following use cases and more:
|
|||
* Intranet / internal Wiki
|
||||
* 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")
|
||||
|
||||
|
@ -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
|
||||
* [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
|
||||
|
||||
## Prerequisites
|
||||
## Setup and 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`.
|
||||
|
||||
## Deployment
|
||||
|
||||
### 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”.
|
||||
|
@ -116,20 +118,19 @@ terraform destroy
|
|||
|
||||
The above command will delete the associated resources so there will be no billable charges made afterwards.
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Variables
|
||||
|
||||
| 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> | ✓ | |
|
||||
| [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>"allUsers"</code> |
|
||||
| [cloudsql_password](variables.tf#L24) | CloudSQL password (will be randomly generated by default). | <code>string</code> | | <code>null</code> |
|
||||
| [connector](variables.tf#L30) | Existing VPC serverless connector to use if not creating a new one. | <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> |
|
||||
| [ip_ranges](variables.tf#L43) | CIDR blocks: VPC serverless connector, Private Service Access(PSA) for CloudSQL, CloudSQL VPC. | <code title="object({ connector = string psa = string sql_vpc = string })">object({…})</code> | | <code title="{ connector = "10.8.0.0/28" psa = "10.60.0.0/24" sql_vpc = "10.0.0.0/20" }">{…}</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(string)</code> | | <code>[]</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> |
|
||||
| [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>"allUsers"</code> |
|
||||
| [cloudsql_password](variables.tf#L30) | CloudSQL password (will be randomly generated by default). | <code>string</code> | | <code>null</code> |
|
||||
| [connector](variables.tf#L36) | Existing VPC serverless connector to use if not creating a new one. | <code>string</code> | | <code>null</code> |
|
||||
| [create_connector](variables.tf#L42) | Should a VPC serverless connector be created or not. | <code>bool</code> | | <code>true</code> |
|
||||
| [ip_ranges](variables.tf#L49) | CIDR blocks: VPC serverless connector, Private Service Access(PSA) for CloudSQL, CloudSQL VPC. | <code title="object({ connector = string psa = string sql_vpc = string })">object({…})</code> | | <code title="{ connector = "10.8.0.0/28" psa = "10.60.0.0/24" sql_vpc = "10.0.0.0/20" }">{…}</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({ billing_account_id = string parent = string })">object({…})</code> | | <code>null</code> |
|
||||
| [region](variables.tf#L86) | Region for the created resources. | <code>string</code> | | <code>"europe-west4"</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. | ✓ |
|
||||
| [wp_password](outputs.tf#L29) | Wordpress user password. | ✓ |
|
||||
| [wp_user](outputs.tf#L35) | Wordpress username. | |
|
||||
|
||||
<!-- 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
|
||||
```
|
||||
|
|
|
@ -16,30 +16,29 @@
|
|||
|
||||
|
||||
locals {
|
||||
all_principals_iam = [for k in var.principals : "user:${k}"]
|
||||
cloudsql_conf = {
|
||||
database_version = "MYSQL_8_0"
|
||||
tier = "db-g1-small"
|
||||
db = "wp-mysql"
|
||||
user = "admin"
|
||||
}
|
||||
iam = {
|
||||
# CloudSQL
|
||||
"roles/cloudsql.admin" = local.all_principals_iam
|
||||
"roles/cloudsql.client" = local.all_principals_iam
|
||||
"roles/cloudsql.instanceUser" = local.all_principals_iam
|
||||
# common roles
|
||||
"roles/logging.admin" = local.all_principals_iam
|
||||
"roles/iam.serviceAccountUser" = local.all_principals_iam
|
||||
"roles/iam.serviceAccountTokenCreator" = local.all_principals_iam
|
||||
}
|
||||
iam_roles = [
|
||||
"roles/cloudsql.admin",
|
||||
"roles/cloudsql.client",
|
||||
"roles/cloudsql.instanceUser",
|
||||
"roles/logging.admin",
|
||||
"roles/iam.serviceAccountUser",
|
||||
"roles/iam.serviceAccountTokenCreator"
|
||||
]
|
||||
connector = var.connector == null ? google_vpc_access_connector.connector.0.self_link : var.connector
|
||||
wp_user = "user"
|
||||
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" {
|
||||
source = "../../../../modules/project"
|
||||
name = var.project_id
|
||||
|
@ -47,8 +46,19 @@ module "project" {
|
|||
billing_account = try(var.project_create.billing_account_id, null)
|
||||
project_create = var.project_create != null
|
||||
prefix = var.project_create == null ? null : var.prefix
|
||||
iam = var.project_create != null ? local.iam : {}
|
||||
iam_additive = var.project_create == null ? local.iam : {}
|
||||
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 = [
|
||||
"run.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" {
|
||||
source = "../../../../modules/cloud-run"
|
||||
project_id = module.project.project_id
|
||||
name = "${var.prefix}-cr-wordpress"
|
||||
region = var.region
|
||||
|
||||
containers = [{
|
||||
image = var.wordpress_image
|
||||
ports = [{
|
||||
name = "http1"
|
||||
protocol = null
|
||||
container_port = var.wordpress_port
|
||||
}]
|
||||
options = {
|
||||
command = null
|
||||
args = null
|
||||
env_from = null
|
||||
# set up the database connection
|
||||
containers = {
|
||||
wp = {
|
||||
image = var.wordpress_image
|
||||
ports = {
|
||||
http = { container_port = var.wordpress_port }
|
||||
}
|
||||
env = {
|
||||
"APACHE_HTTP_PORT_NUMBER" : var.wordpress_port
|
||||
"WORDPRESS_DATABASE_HOST" : module.cloudsql.ip
|
||||
"WORDPRESS_DATABASE_NAME" : local.cloudsql_conf.db
|
||||
"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_USERNAME" : local.wp_user
|
||||
"WORDPRESS_PASSWORD" : local.wp_pass
|
||||
APACHE_HTTP_PORT_NUMBER = var.wordpress_port
|
||||
WORDPRESS_DATABASE_HOST = module.cloudsql.ip
|
||||
WORDPRESS_DATABASE_NAME = local.cloudsql_conf.db
|
||||
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_USERNAME = local.wp_user
|
||||
WORDPRESS_PASSWORD = local.wp_pass
|
||||
}
|
||||
}
|
||||
resources = null
|
||||
volume_mounts = null
|
||||
}]
|
||||
|
||||
}
|
||||
iam = {
|
||||
"roles/run.invoker" : [var.cloud_run_invoker]
|
||||
}
|
||||
|
||||
revision_annotations = {
|
||||
autoscaling = {
|
||||
min_scale = 1
|
||||
max_scale = 2
|
||||
}
|
||||
# connect to CloudSQL
|
||||
cloudsql_instances = [module.cloudsql.connection_name]
|
||||
vpcaccess_connector = null
|
||||
cloudsql_instances = [module.cloudsql.connection_name]
|
||||
# allow all traffic
|
||||
vpcaccess_egress = "all-traffic"
|
||||
vpcaccess_connector = local.connector
|
||||
vpcaccess_egress = "all-traffic"
|
||||
}
|
||||
ingress_settings = "all"
|
||||
}
|
||||
|
|
|
@ -14,6 +14,12 @@
|
|||
* 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
|
||||
variable "cloud_run_invoker" {
|
||||
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" {
|
||||
description = "Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format."
|
||||
type = object({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2022 Google LLC
|
||||
# Copyright 2023 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -13,8 +13,6 @@
|
|||
# limitations under the License.
|
||||
|
||||
default:
|
||||
before_script:
|
||||
- echo "$${CI_JOB_JWT_V2}" > token.txt
|
||||
image:
|
||||
name: hashicorp/terraform
|
||||
entrypoint:
|
||||
|
@ -27,7 +25,9 @@ variables:
|
|||
FAST_SERVICE_ACCOUNT: ${service_account}
|
||||
FAST_WIF_PROVIDER: ${identity_provider}
|
||||
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
%{~ if tf_providers_file != "" ~}
|
||||
TF_PROVIDERS_FILE: ${tf_providers_file}
|
||||
%{~ endif ~}
|
||||
TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)}
|
||||
|
||||
stages:
|
||||
|
@ -40,13 +40,26 @@ cache:
|
|||
key: gcp-auth
|
||||
paths:
|
||||
- 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:
|
||||
id_tokens:
|
||||
GITLAB_TOKEN:
|
||||
aud:
|
||||
%{~ for aud in audiences ~}
|
||||
- ${aud}
|
||||
%{~ endfor ~}
|
||||
image:
|
||||
name: google/cloud-sdk:slim
|
||||
stage: gcp-auth
|
||||
script:
|
||||
- echo "$${GITLAB_TOKEN}" > token.txt
|
||||
- |
|
||||
gcloud iam workload-identity-pools create-cred-config \
|
||||
$${FAST_WIF_PROVIDER} \
|
||||
|
@ -54,6 +67,7 @@ gcp-auth:
|
|||
--service-account-token-lifetime-seconds=3600 \
|
||||
--output-file=$${GOOGLE_CREDENTIALS} \
|
||||
--credential-source-file=token.txt
|
||||
|
||||
tf-files:
|
||||
dependencies:
|
||||
- gcp-auth
|
||||
|
@ -63,15 +77,18 @@ tf-files:
|
|||
script:
|
||||
# - gcloud components install -q alpha
|
||||
- gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS}
|
||||
- mkdir -p .tf-setup
|
||||
- |
|
||||
gcloud alpha storage cp -r \
|
||||
"gs://$${FAST_OUTPUTS_BUCKET}/providers/$${TF_PROVIDERS_FILE}" .tf-setup/
|
||||
- |
|
||||
gcloud alpha storage cp -r \
|
||||
"gs://$${FAST_OUTPUTS_BUCKET}/tfvars" .tf-setup/
|
||||
%{~ if tf_providers_file != "" ~}
|
||||
- gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/providers/${tf_providers_file}" ./
|
||||
%{~ endif ~}
|
||||
%{~ for f in tf_var_files ~}
|
||||
- gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/tfvars/${f}" ./
|
||||
%{~ endfor ~}
|
||||
- ls -l
|
||||
|
||||
tf-plan:
|
||||
dependencies:
|
||||
- tf-files
|
||||
stage: tf-plan
|
||||
# uncomment the following lines and set the SSH key secret for private modules repo
|
||||
# before_script:
|
||||
# - |
|
||||
|
@ -80,20 +97,15 @@ tf-plan:
|
|||
# mkdir -p ~/.ssh
|
||||
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
|
||||
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
|
||||
stage: tf-plan
|
||||
script:
|
||||
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
|
||||
- |
|
||||
for f in $${TF_VAR_FILES}; do
|
||||
ln -s ".tf-setup/tfvars/$f" ./
|
||||
done
|
||||
- terraform init
|
||||
- terraform validate
|
||||
- terraform plan
|
||||
dependencies:
|
||||
- tf-files
|
||||
|
||||
tf-apply:
|
||||
dependencies:
|
||||
- tf-files
|
||||
stage: tf-apply
|
||||
# uncomment the following lines and set the SSH key secret for private modules repo
|
||||
# before_script:
|
||||
# - |
|
||||
|
@ -102,18 +114,10 @@ tf-apply:
|
|||
# mkdir -p ~/.ssh
|
||||
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
|
||||
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
|
||||
stage: tf-apply
|
||||
script:
|
||||
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
|
||||
- |
|
||||
for f in $${TF_VAR_FILES}; do
|
||||
ln -s ".tf-setup/tfvars/$f" ./
|
||||
done
|
||||
- terraform init
|
||||
- terraform validate
|
||||
- terraform apply -input=false -auto-approve
|
||||
dependencies:
|
||||
- tf-files
|
||||
when: manual
|
||||
only:
|
||||
variables:
|
||||
|
|
|
@ -175,7 +175,6 @@ This configuration is possible but unsupported and only exists for development p
|
|||
|
||||
<!-- TFDOC OPTS files:1 show_extra:1 -->
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Files
|
||||
|
||||
| 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> |
|
||||
| [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> | |
|
||||
| [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-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | <code>google_storage_bucket_object</code> |
|
||||
| [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 |
|
||||
|---|---|:---:|:---:|:---:|:---:|
|
||||
| [automation](variables.tf#L20) | Automation resources created by the organization-level bootstrap stage. | <code title="object({ outputs_bucket = string project_id = string project_number = string federated_identity_pool = string federated_identity_providers = map(object({ issuer = string issuer_uri = string name = string principal_tpl = string principalset_tpl = string })) })">object({…})</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({ id = string is_org_level = optional(bool, true) no_iam = optional(bool, false) })">object({…})</code> | ✓ | | |
|
||||
| [organization](variables.tf#L191) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</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> |
|
||||
| [tag_keys](variables.tf#L230) | Organization tag keys. | <code title="object({ context = string environment = string tenant = string })">object({…})</code> | ✓ | | <code>1-resman</code> |
|
||||
| [tag_names](variables.tf#L241) | Customized names for resource management tags. | <code title="object({ context = string environment = string tenant = string })">object({…})</code> | ✓ | | <code>1-resman</code> |
|
||||
| [tag_values](variables.tf#L252) | Organization resource management tag values. | <code>map(string)</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({ descriptive_name = string groups = object({ gcp-admins = string gcp-devops = optional(string) gcp-network-admins = optional(string) gcp-security-admins = optional(string) }) short_name = string short_name_is_prefix = optional(bool, false) fast_features = optional(object({ data_platform = optional(bool) gke = optional(bool) project_factory = optional(bool) sandbox = optional(bool) teams = optional(bool) }), {}) locations = optional(object({ bq = optional(string) gcs = optional(string) logging = optional(string) pubsub = optional(list(string)) }), {}) })">object({…})</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({ bootstrap = optional(object({ branch = optional(string) identity_provider = string name = string type = string })) resman = optional(object({ branch = optional(string) identity_provider = string name = string type = string })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [custom_roles](variables.tf#L94) | Custom roles defined at the organization level, in key => id format. | <code title="object({ service_project_network_admin = string tenant_network_admin = string })">object({…})</code> | | <code>null</code> | <code>0-bootstrap</code> |
|
||||
| [fast_features](variables.tf#L104) | Selective control for top-level FAST features. | <code title="object({ data_platform = optional(bool, true) gke = optional(bool, true) project_factory = optional(bool, true) sandbox = optional(bool, true) teams = optional(bool, true) })">object({…})</code> | | <code>{}</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(object({ attribute_condition = string issuer = string custom_settings = object({ issuer_uri = string allowed_audiences = list(string) }) }))">map(object({…}))</code> | | <code>{}</code> | |
|
||||
| [group_iam](variables.tf#L132) | Tenant-level custom group IAM settings in group => [roles] format. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [iam](variables.tf#L138) | Tenant-level custom IAM settings in role => [principal] format. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [iam_additive](variables.tf#L144) | Tenant-level custom IAM settings in role => [principal] format for non-authoritative bindings. | <code>map(list(string))</code> | | <code>{}</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({ bq = string gcs = string logging = string pubsub = list(string) })">object({…})</code> | | <code title="{ bq = "EU" gcs = "EU" logging = "global" pubsub = [] }">{…}</code> | <code>0-bootstrap</code> |
|
||||
| [log_sinks](variables.tf#L170) | Tenant-level log sinks, in name => {type, filter} format. | <code title="map(object({ filter = string type = string }))">map(object({…}))</code> | | <code title="{ audit-logs = { filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\"" type = "logging" } }">{…}</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> | |
|
||||
| [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({ automation = string logging = string })">object({…})</code> | | <code title="{ automation = null logging = 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> | |
|
||||
| [automation](variables.tf#L20) | Automation resources created by the organization-level bootstrap stage. | <code title="object({ outputs_bucket = string project_id = string project_number = string federated_identity_pool = string federated_identity_providers = map(object({ audiences = list(string) issuer = string issuer_uri = string name = string principal_tpl = string principalset_tpl = string })) })">object({…})</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({ id = string is_org_level = optional(bool, true) no_iam = optional(bool, false) })">object({…})</code> | ✓ | | |
|
||||
| [organization](variables.tf#L214) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</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#L253) | Organization tag keys. | <code title="object({ context = string environment = string tenant = string })">object({…})</code> | ✓ | | <code>1-resman</code> |
|
||||
| [tag_names](variables.tf#L264) | Customized names for resource management tags. | <code title="object({ context = string environment = string tenant = string })">object({…})</code> | ✓ | | <code>1-resman</code> |
|
||||
| [tag_values](variables.tf#L275) | Organization resource management tag values. | <code>map(string)</code> | ✓ | | <code>1-resman</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({ descriptive_name = string groups = object({ gcp-admins = string gcp-devops = optional(string) gcp-network-admins = optional(string) gcp-security-admins = optional(string) }) short_name = string short_name_is_prefix = optional(bool, false) fast_features = optional(object({ data_platform = optional(bool) gke = optional(bool) project_factory = optional(bool) sandbox = optional(bool) teams = optional(bool) }), {}) locations = optional(object({ bq = optional(string) gcs = optional(string) logging = optional(string) pubsub = optional(list(string)) }), {}) })">object({…})</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({ bootstrap = optional(object({ branch = optional(string) identity_provider = string name = string type = string })) resman = optional(object({ branch = optional(string) identity_provider = string name = string type = string })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [custom_roles](variables.tf#L95) | Custom roles defined at the organization level, in key => id format. | <code title="object({ service_project_network_admin = string tenant_network_admin = string })">object({…})</code> | | <code>null</code> | <code>0-bootstrap</code> |
|
||||
| [fast_features](variables.tf#L105) | Selective control for top-level FAST features. | <code title="object({ data_platform = optional(bool, true) gke = optional(bool, true) project_factory = optional(bool, true) sandbox = optional(bool, true) teams = optional(bool, true) })">object({…})</code> | | <code>{}</code> | <code>0-bootstrap</code> |
|
||||
| [federated_identity_providers](variables.tf#L119) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | <code title="map(object({ attribute_condition = optional(string) issuer = string custom_settings = optional(object({ issuer_uri = optional(string) audiences = optional(list(string), []) }), {}) }))">map(object({…}))</code> | | <code>{}</code> | |
|
||||
| [group_iam](variables.tf#L133) | Tenant-level custom group IAM settings in group => [roles] format. | <code>map(list(string))</code> | | <code>{}</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({ gcp-devops = optional(string) gcp-network-admins = optional(string) gcp-security-admins = optional(string) })">object({…})</code> | | <code>{}</code> | <code>0-bootstrap</code> |
|
||||
| [iam](variables.tf#L152) | Tenant-level custom IAM settings in role => [principal] format. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [iam_bindings_additive](variables.tf#L158) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</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({ bq = string gcs = string logging = string pubsub = list(string) })">object({…})</code> | | <code title="{ bq = "EU" gcs = "EU" logging = "global" pubsub = [] }">{…}</code> | <code>0-bootstrap</code> |
|
||||
| [log_sinks](variables.tf#L193) | Tenant-level log sinks, in name => {type, filter} format. | <code title="map(object({ filter = string type = string }))">map(object({…}))</code> | | <code title="{ audit-logs = { filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\"" type = "logging" } }">{…}</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> | |
|
||||
| [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({ automation = string logging = string })">object({…})</code> | | <code title="{ automation = null logging = null }">{…}</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
|
||||
|
||||
| name | description | sensitive | consumers |
|
||||
|---|---|:---:|---|
|
||||
| [cicd_workflows](outputs.tf#L107) | CI/CD workflows for tenant bootstrap and resource management stages. | ✓ | |
|
||||
| [federated_identity](outputs.tf#L113) | Workload Identity Federation pool and providers. | | |
|
||||
| [provider](outputs.tf#L123) | Terraform provider file for tenant resource management stage. | ✓ | <code>stage-01</code> |
|
||||
| [tenant_resources](outputs.tf#L130) | Tenant-level resources. | | |
|
||||
| [tfvars](outputs.tf#L141) | Terraform variable files for the following tenant stages. | ✓ | |
|
||||
|
||||
| [cicd_workflows](outputs.tf#L109) | CI/CD workflows for tenant bootstrap and resource management stages. | ✓ | |
|
||||
| [federated_identity](outputs.tf#L115) | Workload Identity Federation pool and providers. | | |
|
||||
| [provider](outputs.tf#L125) | Terraform provider file for tenant resource management stage. | ✓ | <code>stage-01</code> |
|
||||
| [tenant_resources](outputs.tf#L132) | Tenant-level resources. | | |
|
||||
| [tfvars](outputs.tf#L143) | Terraform variable files for the following tenant stages. | ✓ | |
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -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
|
||||
# TODO: move to new iam_bindings_additive in the organization module
|
||||
|
||||
resource "google_organization_iam_member" "org_policy_admin_stage2_3" {
|
||||
for_each = {
|
||||
|
|
|
@ -20,23 +20,27 @@ locals {
|
|||
_file_prefix = "tenants/${var.tenant_config.short_name}"
|
||||
# derive identity pool names from identity providers for easy reference
|
||||
cicd_identity_pools = {
|
||||
for k, v in local.cicd_identity_providers :
|
||||
for k, v in local.cicd_providers :
|
||||
k => split("/providers/", v.name)[0]
|
||||
}
|
||||
# merge org-level and tenant-level identity providers
|
||||
cicd_identity_providers = merge(
|
||||
cicd_providers = merge(
|
||||
var.automation.federated_identity_providers,
|
||||
{
|
||||
for k, v in google_iam_workload_identity_pool_provider.default :
|
||||
k => {
|
||||
audiences = concat(
|
||||
v.oidc[0].allowed_audiences,
|
||||
["https://iam.googleapis.com/${v.name}"]
|
||||
)
|
||||
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
|
||||
principal_tpl = local.identity_providers[k].principal_tpl
|
||||
principalset_tpl = local.identity_providers[k].principalset_tpl
|
||||
}
|
||||
})
|
||||
# filter CI/CD repositories to only keep valid ones
|
||||
}
|
||||
)
|
||||
cicd_repositories = {
|
||||
for k, v in coalesce(var.cicd_repositories, {}) : k => v
|
||||
if(
|
||||
|
@ -46,7 +50,7 @@ locals {
|
|||
try(v.type, null) == "sourcerepo"
|
||||
||
|
||||
contains(
|
||||
keys(local.cicd_identity_providers),
|
||||
keys(local.cicd_providers),
|
||||
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
|
||||
|
@ -111,12 +118,12 @@ module "automation-tf-cicd-sa-bootstrap" {
|
|||
"roles/iam.workloadIdentityUser" = [
|
||||
each.value.branch == null
|
||||
? 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],
|
||||
each.value.name
|
||||
)
|
||||
: 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],
|
||||
each.value.name,
|
||||
each.value.branch
|
||||
|
@ -141,10 +148,11 @@ module "automation-tf-org-resman-sa" {
|
|||
project_id = var.automation.project_id
|
||||
name = local.resman_sa
|
||||
service_account_create = false
|
||||
iam_additive = {
|
||||
"roles/iam.serviceAccountTokenCreator" = compact([
|
||||
try(module.automation-tf-cicd-sa-bootstrap["0"].iam_email, null)
|
||||
])
|
||||
iam_bindings_additive = local.cicd_sa_resman == null ? {} : {
|
||||
sa_resman_cicd = {
|
||||
member = local.cicd_sa_resman
|
||||
role = "roles/iam.serviceAccountTokenCreator"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,12 +209,12 @@ module "automation-tf-cicd-sa-resman" {
|
|||
"roles/iam.workloadIdentityUser" = [
|
||||
each.value.branch == null
|
||||
? 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],
|
||||
each.value.name
|
||||
)
|
||||
: 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],
|
||||
each.value.name,
|
||||
each.value.branch
|
||||
|
|
|
@ -38,7 +38,7 @@ locals {
|
|||
principal_tpl = "principal://iam.googleapis.com/%s/subject/repo:%s:ref:refs/heads/%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 = {
|
||||
attribute_mapping = {
|
||||
"google.subject" = "assertion.sub"
|
||||
|
@ -56,10 +56,9 @@ locals {
|
|||
"attribute.ref_protected" = "assertion.ref_protected"
|
||||
"attribute.ref_type" = "assertion.ref_type"
|
||||
}
|
||||
allowed_audiences = ["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"
|
||||
principalset_tpl = "principalSet://iam.googleapis.com/%s/attribute.repository/%s"
|
||||
issuer_uri = "https://gitlab.com"
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,13 +81,11 @@ resource "google_iam_workload_identity_pool_provider" "default" {
|
|||
attribute_condition = each.value.attribute_condition
|
||||
attribute_mapping = each.value.attribute_mapping
|
||||
oidc {
|
||||
allowed_audiences = (
|
||||
try(each.value.custom_settings.allowed_audiences, null) != null
|
||||
? each.value.custom_settings.allowed_audiences
|
||||
: try(each.value.allowed_audiences, null)
|
||||
)
|
||||
# Setting an empty list configures allowed_audiences to the url of the provider
|
||||
allowed_audiences = each.value.custom_settings.audiences
|
||||
# If users don't provide an issuer_uri, we set the public one for the plaform choosed.
|
||||
issuer_uri = (
|
||||
try(each.value.custom_settings.issuer_uri, null) != null
|
||||
each.value.custom_settings.issuer_uri != null
|
||||
? each.value.custom_settings.issuer_uri
|
||||
: try(each.value.issuer_uri, null)
|
||||
)
|
||||
|
|
|
@ -95,6 +95,6 @@ module "tenant-folder-iam" {
|
|||
module.automation-tf-resman-sa.iam_email
|
||||
]
|
||||
})
|
||||
iam_additive = var.iam_additive
|
||||
depends_on = [module.automation-project]
|
||||
iam_bindings_additive = var.iam_bindings_additive
|
||||
depends_on = [module.automation-project]
|
||||
}
|
||||
|
|
|
@ -26,19 +26,45 @@ locals {
|
|||
module "organization" {
|
||||
source = "../../../modules/organization"
|
||||
organization_id = "organizations/${var.organization.id}"
|
||||
iam_additive = merge(
|
||||
iam_bindings_additive = merge(
|
||||
{
|
||||
"roles/resourcemanager.organizationViewer" = [
|
||||
"group:${local.groups.gcp-admins}"
|
||||
]
|
||||
admins_org_viewer = {
|
||||
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" ? {
|
||||
"roles/billing.admin" = [
|
||||
"group:${local.groups.gcp-admins}",
|
||||
module.automation-tf-resman-sa.iam_email
|
||||
]
|
||||
"roles/billing.costsManager" = ["group:${local.groups.gcp-admins}"]
|
||||
} : {}
|
||||
local.billing_mode != "org" ? {} : {
|
||||
admins_billing_admin = {
|
||||
member = "group:${local.groups.gcp-admins}"
|
||||
role = "roles/billing.admin"
|
||||
}
|
||||
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 = {
|
||||
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" {
|
||||
for_each = var.tag_values
|
||||
tag_value = each.value
|
||||
|
@ -64,21 +92,4 @@ resource "google_tags_tag_value_iam_member" "admins_tag_viewer" {
|
|||
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
|
||||
|
|
|
@ -20,8 +20,9 @@ locals {
|
|||
"${path.module}/templates/workflow-${v.type}.yaml", (
|
||||
k == "bootstrap"
|
||||
? {
|
||||
audiences = local.cicd_providers[v["identity_provider"]].audiences
|
||||
identity_provider = try(
|
||||
local.cicd_identity_providers[v["identity_provider"]].name, ""
|
||||
local.cicd_providers[v["identity_provider"]].name, ""
|
||||
)
|
||||
outputs_bucket = var.automation.outputs_bucket
|
||||
service_account = try(
|
||||
|
@ -36,8 +37,9 @@ locals {
|
|||
]
|
||||
}
|
||||
: {
|
||||
audiences = local.cicd_providers[v["identity_provider"]].audiences
|
||||
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
|
||||
service_account = try(
|
||||
|
@ -70,7 +72,7 @@ locals {
|
|||
try(google_iam_workload_identity_pool.default.0.name, null),
|
||||
var.automation.federated_identity_pool,
|
||||
])
|
||||
federated_identity_providers = local.cicd_identity_providers
|
||||
federated_identity_providers = local.cicd_providers
|
||||
service_accounts = merge(
|
||||
{ resman = module.automation-tf-resman-sa.email },
|
||||
{
|
||||
|
@ -116,7 +118,7 @@ output "federated_identity" {
|
|||
pool = try(
|
||||
google_iam_workload_identity_pool.default.0.name, null
|
||||
)
|
||||
providers = local.cicd_identity_providers
|
||||
providers = local.cicd_providers
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2022 Google LLC
|
||||
# Copyright 2023 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -13,8 +13,6 @@
|
|||
# limitations under the License.
|
||||
|
||||
default:
|
||||
before_script:
|
||||
- echo "$${CI_JOB_JWT_V2}" > token.txt
|
||||
image:
|
||||
name: hashicorp/terraform
|
||||
entrypoint:
|
||||
|
@ -42,13 +40,26 @@ cache:
|
|||
key: gcp-auth
|
||||
paths:
|
||||
- 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:
|
||||
id_tokens:
|
||||
GITLAB_TOKEN:
|
||||
aud:
|
||||
%{~ for aud in audiences ~}
|
||||
- ${aud}
|
||||
%{~ endfor ~}
|
||||
image:
|
||||
name: google/cloud-sdk:slim
|
||||
stage: gcp-auth
|
||||
script:
|
||||
- echo "$${GITLAB_TOKEN}" > token.txt
|
||||
- |
|
||||
gcloud iam workload-identity-pools create-cred-config \
|
||||
$${FAST_WIF_PROVIDER} \
|
||||
|
@ -56,6 +67,7 @@ gcp-auth:
|
|||
--service-account-token-lifetime-seconds=3600 \
|
||||
--output-file=$${GOOGLE_CREDENTIALS} \
|
||||
--credential-source-file=token.txt
|
||||
|
||||
tf-files:
|
||||
dependencies:
|
||||
- gcp-auth
|
||||
|
@ -65,17 +77,18 @@ tf-files:
|
|||
script:
|
||||
# - gcloud components install -q alpha
|
||||
- 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}" .tf-setup/
|
||||
- gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/providers/${tf_providers_file}" ./
|
||||
%{~ endif ~}
|
||||
- |
|
||||
gcloud alpha storage cp -r \
|
||||
"gs://$${FAST_OUTPUTS_BUCKET}/tfvars" .tf-setup/
|
||||
%{~ for f in tf_var_files ~}
|
||||
- gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/tfvars/${f}" ./
|
||||
%{~ endfor ~}
|
||||
- ls -l
|
||||
|
||||
tf-plan:
|
||||
dependencies:
|
||||
- tf-files
|
||||
stage: tf-plan
|
||||
# uncomment the following lines and set the SSH key secret for private modules repo
|
||||
# before_script:
|
||||
# - |
|
||||
|
@ -84,20 +97,15 @@ tf-plan:
|
|||
# mkdir -p ~/.ssh
|
||||
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
|
||||
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
|
||||
stage: tf-plan
|
||||
script:
|
||||
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
|
||||
- |
|
||||
for f in $${TF_VAR_FILES}; do
|
||||
ln -s ".tf-setup/tfvars/$f" ./
|
||||
done
|
||||
- terraform init
|
||||
- terraform validate
|
||||
- terraform plan
|
||||
dependencies:
|
||||
- tf-files
|
||||
|
||||
tf-apply:
|
||||
dependencies:
|
||||
- tf-files
|
||||
stage: tf-apply
|
||||
# uncomment the following lines and set the SSH key secret for private modules repo
|
||||
# before_script:
|
||||
# - |
|
||||
|
@ -106,18 +114,10 @@ tf-apply:
|
|||
# mkdir -p ~/.ssh
|
||||
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
|
||||
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
|
||||
stage: tf-apply
|
||||
script:
|
||||
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
|
||||
- |
|
||||
for f in $${TF_VAR_FILES}; do
|
||||
ln -s ".tf-setup/tfvars/$f" ./
|
||||
done
|
||||
- terraform init
|
||||
- terraform validate
|
||||
- terraform apply -input=false -auto-approve
|
||||
dependencies:
|
||||
- tf-files
|
||||
when: manual
|
||||
only:
|
||||
variables:
|
||||
|
|
|
@ -26,6 +26,7 @@ variable "automation" {
|
|||
project_number = string
|
||||
federated_identity_pool = string
|
||||
federated_identity_providers = map(object({
|
||||
audiences = list(string)
|
||||
issuer = string
|
||||
issuer_uri = string
|
||||
name = string
|
||||
|
@ -118,12 +119,12 @@ variable "fast_features" {
|
|||
variable "federated_identity_providers" {
|
||||
description = "Workload Identity Federation pools. The `cicd_repositories` variable references keys here."
|
||||
type = map(object({
|
||||
attribute_condition = string
|
||||
attribute_condition = optional(string)
|
||||
issuer = string
|
||||
custom_settings = object({
|
||||
issuer_uri = string
|
||||
allowed_audiences = list(string)
|
||||
})
|
||||
custom_settings = optional(object({
|
||||
issuer_uri = optional(string)
|
||||
audiences = optional(list(string), [])
|
||||
}), {})
|
||||
}))
|
||||
default = {}
|
||||
nullable = false
|
||||
|
@ -135,16 +136,38 @@ variable "group_iam" {
|
|||
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" {
|
||||
description = "Tenant-level custom IAM settings in role => [principal] format."
|
||||
type = map(list(string))
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "iam_additive" {
|
||||
description = "Tenant-level custom IAM settings in role => [principal] format for non-authoritative bindings."
|
||||
type = map(list(string))
|
||||
default = {}
|
||||
variable "iam_bindings_additive" {
|
||||
description = "Individual additive IAM bindings. Keys are arbitrary."
|
||||
type = map(object({
|
||||
member = string
|
||||
role = string
|
||||
condition = optional(object({
|
||||
expression = string
|
||||
title = string
|
||||
description = optional(string)
|
||||
}))
|
||||
}))
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "locations" {
|
||||
|
|
|
@ -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 -->
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Files
|
||||
|
||||
| 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 |
|
||||
|---|---|:---:|:---:|:---:|:---:|
|
||||
| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | <code title="object({ outputs_bucket = string project_id = string project_number = string federated_identity_pools = list(string) federated_identity_providers = map(object({ issuer = string issuer_uri = string name = string principal_tpl = string principalset_tpl = string })) service_accounts = object({ networking = string resman = string security = string dp-dev = optional(string) dp-prod = optional(string) gke-dev = optional(string) gke-prod = optional(string) pf-dev = optional(string) pf-prod = optional(string) sandbox = optional(string) teams = optional(string) }) })">object({…})</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({ id = string is_org_level = optional(bool, true) no_iam = optional(bool, false) })">object({…})</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [organization](variables.tf#L204) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</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> |
|
||||
| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | <code title="object({ outputs_bucket = string project_id = string project_number = string federated_identity_pools = list(string) federated_identity_providers = map(object({ audiences = list(string) issuer = string issuer_uri = string name = string principal_tpl = string principalset_tpl = string })) service_accounts = object({ networking = string resman = string security = string dp-dev = optional(string) dp-prod = optional(string) gke-dev = optional(string) gke-prod = optional(string) pf-dev = optional(string) pf-prod = optional(string) sandbox = optional(string) teams = optional(string) }) })">object({…})</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({ id = string is_org_level = optional(bool, true) no_iam = optional(bool, false) })">object({…})</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [organization](variables.tf#L205) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</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> | ✓ | | |
|
||||
| [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({ keys = object({ context = string environment = string tenant = string }) names = object({ context = string environment = string tenant = string }) values = map(string) })">object({…})</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({ data_platform_dev = object({ branch = string identity_provider = string name = string type = string }) data_platform_prod = object({ branch = string identity_provider = string name = string type = string }) gke_dev = object({ branch = string identity_provider = string name = string type = string }) gke_prod = object({ branch = string identity_provider = string name = string type = string }) networking = object({ branch = string identity_provider = string name = string type = string }) project_factory_dev = object({ branch = string identity_provider = string name = string type = string }) project_factory_prod = object({ branch = string identity_provider = string name = string type = string }) security = object({ branch = string identity_provider = string name = string type = string }) })">object({…})</code> | | <code>null</code> | |
|
||||
| [custom_roles](variables.tf#L144) | Custom roles defined at the org level, in key => id format. | <code title="object({ service_project_network_admin = string })">object({…})</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>"data"</code> | |
|
||||
| [fast_features](variables.tf#L159) | Selective control for top-level FAST features. | <code title="object({ data_platform = optional(bool, false) gke = optional(bool, false) project_factory = optional(bool, false) sandbox = optional(bool, false) teams = optional(bool, false) })">object({…})</code> | | <code>{}</code> | <code>0-0-bootstrap</code> |
|
||||
| [groups](variables.tf#L173) | Group names to grant organization-level permissions. | <code title="object({ gcp-devops = optional(string) gcp-network-admins = optional(string) gcp-security-admins = optional(string) })">object({…})</code> | | <code>{}</code> | <code>0-bootstrap</code> |
|
||||
| [locations](variables.tf#L186) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object({ bq = string gcs = string logging = string pubsub = list(string) })">object({…})</code> | | <code title="{ bq = "EU" gcs = "EU" logging = "global" pubsub = [] }">{…}</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> | |
|
||||
| [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> | |
|
||||
| [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({ data_platform_dev = object({ branch = string identity_provider = string name = string type = string }) data_platform_prod = object({ branch = string identity_provider = string name = string type = string }) gke_dev = object({ branch = string identity_provider = string name = string type = string }) gke_prod = object({ branch = string identity_provider = string name = string type = string }) networking = object({ branch = string identity_provider = string name = string type = string }) project_factory_dev = object({ branch = string identity_provider = string name = string type = string }) project_factory_prod = object({ branch = string identity_provider = string name = string type = string }) security = object({ branch = string identity_provider = string name = string type = string }) })">object({…})</code> | | <code>null</code> | |
|
||||
| [custom_roles](variables.tf#L145) | Custom roles defined at the org level, in key => id format. | <code title="object({ service_project_network_admin = string })">object({…})</code> | | <code>null</code> | <code>0-bootstrap</code> |
|
||||
| [data_dir](variables.tf#L154) | Relative path for the folder storing configuration data. | <code>string</code> | | <code>"data"</code> | |
|
||||
| [fast_features](variables.tf#L160) | Selective control for top-level FAST features. | <code title="object({ data_platform = optional(bool, false) gke = optional(bool, false) project_factory = optional(bool, false) sandbox = optional(bool, false) teams = optional(bool, false) })">object({…})</code> | | <code>{}</code> | <code>0-0-bootstrap</code> |
|
||||
| [groups](variables.tf#L174) | Group names to grant organization-level permissions. | <code title="object({ gcp-devops = optional(string) gcp-network-admins = optional(string) gcp-security-admins = optional(string) })">object({…})</code> | | <code>{}</code> | <code>0-bootstrap</code> |
|
||||
| [locations](variables.tf#L187) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object({ bq = string gcs = string logging = string pubsub = list(string) })">object({…})</code> | | <code title="{ bq = "EU" gcs = "EU" logging = "global" pubsub = [] }">{…}</code> | <code>0-bootstrap</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#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(object({ descriptive_name = string group_iam = map(list(string)) impersonation_groups = list(string) }))">map(object({…}))</code> | | <code>null</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 |
|
||||
|---|---|:---:|---|
|
||||
| [cicd_repositories](outputs.tf#L189) | WIF configuration for CI/CD repositories. | | |
|
||||
| [dataplatform](outputs.tf#L203) | Data for the Data Platform stage. | | |
|
||||
| [gke_multitenant](outputs.tf#L219) | Data for the GKE multitenant stage. | | <code>03-gke-multitenant</code> |
|
||||
| [networking](outputs.tf#L240) | Data for the networking stage. | | |
|
||||
| [project_factories](outputs.tf#L249) | Data for the project factories stage. | | |
|
||||
| [providers](outputs.tf#L264) | Terraform provider files for this stage and dependent stages. | ✓ | <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> |
|
||||
| [security](outputs.tf#L285) | Data for the networking stage. | | <code>02-security</code> |
|
||||
| [teams](outputs.tf#L295) | Data for the teams stage. | | |
|
||||
| [tfvars](outputs.tf#L307) | Terraform variable files for the following stages. | ✓ | |
|
||||
|
||||
| [cicd_repositories](outputs.tf#L192) | WIF configuration for CI/CD repositories. | | |
|
||||
| [dataplatform](outputs.tf#L206) | Data for the Data Platform stage. | | |
|
||||
| [gke_multitenant](outputs.tf#L222) | Data for the GKE multitenant stage. | | <code>03-gke-multitenant</code> |
|
||||
| [networking](outputs.tf#L243) | Data for the networking stage. | | |
|
||||
| [project_factories](outputs.tf#L252) | Data for the project factories stage. | | |
|
||||
| [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#L274) | Data for the sandbox stage. | | <code>xx-sandbox</code> |
|
||||
| [security](outputs.tf#L288) | Data for the networking stage. | | <code>02-security</code> |
|
||||
| [teams](outputs.tf#L298) | Data for the teams stage. | | |
|
||||
| [tfvars](outputs.tf#L310) | Terraform variable files for the following stages. | ✓ | |
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -62,6 +62,9 @@ locals {
|
|||
for k, v in local.cicd_repositories : k => templatefile(
|
||||
"${path.module}/templates/workflow-${v.type}.yaml",
|
||||
merge(local.cicd_workflow_attrs[k], {
|
||||
audiences = try(
|
||||
local.cicd_identity_providers[v.identity_provider].audiences, null
|
||||
)
|
||||
identity_provider = try(
|
||||
local.cicd_identity_providers[v.identity_provider].name, null
|
||||
)
|
||||
|
|
|
@ -26,16 +26,19 @@ module "root-folder" {
|
|||
)
|
||||
name = var.test_skip_data_sources ? "Test" : null
|
||||
# end test attributes
|
||||
iam_additive = {
|
||||
"roles/accesscontextmanager.policyAdmin" = [
|
||||
local.automation_sas_iam.security
|
||||
]
|
||||
"roles/compute.orgFirewallPolicyAdmin" = [
|
||||
local.automation_sas_iam.networking
|
||||
]
|
||||
"roles/compute.xpnAdmin" = [
|
||||
local.automation_sas_iam.networking
|
||||
]
|
||||
iam_bindings_additive = {
|
||||
sa_net_fw_policy_admin = {
|
||||
member = local.automation_sas_iam.networking
|
||||
role = "roles/compute.orgFirewallPolicyAdmin"
|
||||
}
|
||||
sa_net_xpn_admin = {
|
||||
member = local.automation_sas_iam.networking
|
||||
role = "roles/compute.xpnAdmin"
|
||||
}
|
||||
sa_sec_vpcsc_admin = {
|
||||
member = local.automation_sas_iam.security
|
||||
role = "roles/accesscontextmanager.policyAdmin"
|
||||
}
|
||||
}
|
||||
org_policies_data_path = var.organization_policy_data_path
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2022 Google LLC
|
||||
# Copyright 2023 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -13,8 +13,6 @@
|
|||
# limitations under the License.
|
||||
|
||||
default:
|
||||
before_script:
|
||||
- echo "$${CI_JOB_JWT_V2}" > token.txt
|
||||
image:
|
||||
name: hashicorp/terraform
|
||||
entrypoint:
|
||||
|
@ -27,7 +25,9 @@ variables:
|
|||
FAST_SERVICE_ACCOUNT: ${service_account}
|
||||
FAST_WIF_PROVIDER: ${identity_provider}
|
||||
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
%{~ if tf_providers_file != "" ~}
|
||||
TF_PROVIDERS_FILE: ${tf_providers_file}
|
||||
%{~ endif ~}
|
||||
TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)}
|
||||
|
||||
stages:
|
||||
|
@ -40,13 +40,26 @@ cache:
|
|||
key: gcp-auth
|
||||
paths:
|
||||
- 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:
|
||||
id_tokens:
|
||||
GITLAB_TOKEN:
|
||||
aud:
|
||||
%{~ for aud in audiences ~}
|
||||
- ${aud}
|
||||
%{~ endfor ~}
|
||||
image:
|
||||
name: google/cloud-sdk:slim
|
||||
stage: gcp-auth
|
||||
script:
|
||||
- echo "$${GITLAB_TOKEN}" > token.txt
|
||||
- |
|
||||
gcloud iam workload-identity-pools create-cred-config \
|
||||
$${FAST_WIF_PROVIDER} \
|
||||
|
@ -54,6 +67,7 @@ gcp-auth:
|
|||
--service-account-token-lifetime-seconds=3600 \
|
||||
--output-file=$${GOOGLE_CREDENTIALS} \
|
||||
--credential-source-file=token.txt
|
||||
|
||||
tf-files:
|
||||
dependencies:
|
||||
- gcp-auth
|
||||
|
@ -63,15 +77,18 @@ tf-files:
|
|||
script:
|
||||
# - gcloud components install -q alpha
|
||||
- gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS}
|
||||
- mkdir -p .tf-setup
|
||||
- |
|
||||
gcloud alpha storage cp -r \
|
||||
"gs://$${FAST_OUTPUTS_BUCKET}/providers/$${TF_PROVIDERS_FILE}" .tf-setup/
|
||||
- |
|
||||
gcloud alpha storage cp -r \
|
||||
"gs://$${FAST_OUTPUTS_BUCKET}/tfvars" .tf-setup/
|
||||
%{~ if tf_providers_file != "" ~}
|
||||
- gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/providers/${tf_providers_file}" ./
|
||||
%{~ endif ~}
|
||||
%{~ for f in tf_var_files ~}
|
||||
- gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/tfvars/${f}" ./
|
||||
%{~ endfor ~}
|
||||
- ls -l
|
||||
|
||||
tf-plan:
|
||||
dependencies:
|
||||
- tf-files
|
||||
stage: tf-plan
|
||||
# uncomment the following lines and set the SSH key secret for private modules repo
|
||||
# before_script:
|
||||
# - |
|
||||
|
@ -80,20 +97,15 @@ tf-plan:
|
|||
# mkdir -p ~/.ssh
|
||||
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
|
||||
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
|
||||
stage: tf-plan
|
||||
script:
|
||||
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
|
||||
- |
|
||||
for f in $${TF_VAR_FILES}; do
|
||||
ln -s ".tf-setup/tfvars/$f" ./
|
||||
done
|
||||
- terraform init
|
||||
- terraform validate
|
||||
- terraform plan
|
||||
dependencies:
|
||||
- tf-files
|
||||
|
||||
tf-apply:
|
||||
dependencies:
|
||||
- tf-files
|
||||
stage: tf-apply
|
||||
# uncomment the following lines and set the SSH key secret for private modules repo
|
||||
# before_script:
|
||||
# - |
|
||||
|
@ -102,18 +114,10 @@ tf-apply:
|
|||
# mkdir -p ~/.ssh
|
||||
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
|
||||
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
|
||||
stage: tf-apply
|
||||
script:
|
||||
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
|
||||
- |
|
||||
for f in $${TF_VAR_FILES}; do
|
||||
ln -s ".tf-setup/tfvars/$f" ./
|
||||
done
|
||||
- terraform init
|
||||
- terraform validate
|
||||
- terraform apply -input=false -auto-approve
|
||||
dependencies:
|
||||
- tf-files
|
||||
when: manual
|
||||
only:
|
||||
variables:
|
||||
|
|
|
@ -26,6 +26,7 @@ variable "automation" {
|
|||
project_number = string
|
||||
federated_identity_pools = list(string)
|
||||
federated_identity_providers = map(object({
|
||||
audiences = list(string)
|
||||
issuer = string
|
||||
issuer_uri = string
|
||||
name = string
|
||||
|
@ -227,7 +228,6 @@ variable "prefix" {
|
|||
# tfdoc:variable:source 0-bootstrap
|
||||
description = "Prefix used for resources that need unique names. Use 9 characters or less."
|
||||
type = string
|
||||
|
||||
validation {
|
||||
condition = try(length(var.prefix), 0) < 13
|
||||
error_message = "Use a maximum of 12 characters for prefix (which is a combination of org prefix and tenant short name)."
|
||||
|
|
|
@ -209,10 +209,10 @@ Then make sure you have configured the correct values for the following variable
|
|||
- `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`
|
||||
- `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,
|
||||
plus any unused characters from the from the `prefix`.
|
||||
For example, if you specify a `prefix` that is 7 characters long,
|
||||
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,
|
||||
plus any unused characters from the from the `prefix`.
|
||||
For example, if you specify a `prefix` that is 7 characters long,
|
||||
then your `tenant prefix` can have a maximum of 4 characters.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -421,7 +430,7 @@ federated_identity_providers = {
|
|||
attribute_condition = "attribute.namespace_path==\"my-gitlab-org\""
|
||||
issuer = "gitlab"
|
||||
custom_settings = {
|
||||
allowed_audiences = ["https://gitlab.fast.example.com"]
|
||||
audiences = ["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> |
|
||||
| [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. | | |
|
||||
| [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-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | <code>google_storage_bucket_object</code> |
|
||||
| [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 |
|
||||
|---|---|:---:|:---:|:---:|:---:|
|
||||
| [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({ id = string is_org_level = optional(bool, true) no_iam = optional(bool, false) })">object({…})</code> | ✓ | | |
|
||||
| [organization](variables.tf#L206) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</code> | ✓ | | |
|
||||
| [prefix](variables.tf#L221) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | |
|
||||
| [organization](variables.tf#L219) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</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> | |
|
||||
| [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({ bootstrap = optional(object({ branch = string identity_provider = string name = string type = string })) resman = optional(object({ branch = string identity_provider = string name = string type = string })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [custom_role_names](variables.tf#L79) | Names of custom roles defined at the org level. | <code title="object({ organization_iam_admin = string service_project_network_admin = string tenant_network_admin = string })">object({…})</code> | | <code title="{ organization_iam_admin = "organizationIamAdmin" service_project_network_admin = "serviceProjectNetworkAdmin" tenant_network_admin = "tenantNetworkAdmin" }">{…}</code> | |
|
||||
| [custom_roles](variables.tf#L93) | Map of role names => list of permissions to additionally create at the organization level. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [fast_features](variables.tf#L100) | Selective control for top-level FAST features. | <code title="object({ data_platform = optional(bool, false) gke = optional(bool, false) project_factory = optional(bool, false) sandbox = optional(bool, false) teams = optional(bool, false) })">object({…})</code> | | <code>{}</code> | |
|
||||
| [federated_identity_providers](variables.tf#L113) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | <code title="map(object({ attribute_condition = optional(string) issuer = string custom_settings = optional(object({ issuer_uri = optional(string) allowed_audiences = optional(list(string), []) }), {}) }))">map(object({…}))</code> | | <code>{}</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(string)</code> | | <code title="{ gcp-billing-admins = "gcp-billing-admins", gcp-devops = "gcp-devops", gcp-network-admins = "gcp-network-admins" gcp-organization-admins = "gcp-organization-admins" gcp-security-admins = "gcp-security-admins" gcp-support = "gcp-devops" }">{…}</code> | |
|
||||
| [iam](variables.tf#L150) | Organization-level custom IAM settings in role => [principal] format. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [iam_additive](variables.tf#L156) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [locations](variables.tf#L162) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object({ bq = string gcs = string logging = string pubsub = list(string) })">object({…})</code> | | <code title="{ bq = "EU" gcs = "EU" logging = "global" pubsub = [] }">{…}</code> | |
|
||||
| [log_sinks](variables.tf#L181) | Org-level log sinks, in name => {type, filter} format. | <code title="map(object({ filter = string type = string }))">map(object({…}))</code> | | <code title="{ audit-logs = { filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\"" type = "logging" } vpc-sc = { filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\"" type = "logging" } }">{…}</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> | |
|
||||
| [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({ automation = string billing = string logging = string })">object({…})</code> | | <code title="{ automation = null billing = null logging = null }">{…}</code> | |
|
||||
| [federated_identity_providers](variables.tf#L113) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | <code title="map(object({ attribute_condition = optional(string) issuer = string custom_settings = optional(object({ issuer_uri = optional(string) audiences = optional(list(string), []) }), {}) }))">map(object({…}))</code> | | <code>{}</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(list(string))</code> | | <code>{}</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(string)</code> | | <code title="{ gcp-billing-admins = "gcp-billing-admins", gcp-devops = "gcp-devops", gcp-network-admins = "gcp-network-admins" gcp-organization-admins = "gcp-organization-admins" gcp-security-admins = "gcp-security-admins" gcp-support = "gcp-devops" }">{…}</code> | |
|
||||
| [iam](variables.tf#L158) | Organization-level custom IAM settings in role => [principal] format. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||
| [iam_bindings_additive](variables.tf#L165) | Organization-level custom additive IAM bindings. Keys are arbitrary. | <code title="map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> | |
|
||||
| [locations](variables.tf#L180) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object({ bq = optional(string, "EU") gcs = optional(string, "EU") logging = optional(string, "global") pubsub = optional(list(string), []) })">object({…})</code> | | <code>{}</code> | |
|
||||
| [log_sinks](variables.tf#L194) | Org-level log sinks, in name => {type, filter} format. | <code title="map(object({ filter = string type = string }))">map(object({…}))</code> | | <code title="{ audit-logs = { filter = "logName:\"/logs/cloudaudit.googleapis.com%2Factivity\" OR logName:\"/logs/cloudaudit.googleapis.com%2Fsystem_event\"" type = "logging" } vpc-sc = { filter = "protoPayload.metadata.@type=\"type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata\"" type = "logging" } }">{…}</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({ automation = string billing = string logging = string })">object({…})</code> | | <code title="{ automation = null billing = null logging = null }">{…}</code> | |
|
||||
|
||||
## Outputs
|
||||
|
||||
| name | description | sensitive | consumers |
|
||||
|---|---|:---:|---|
|
||||
| [automation](outputs.tf#L100) | Automation resources. | | |
|
||||
| [billing_dataset](outputs.tf#L105) | BigQuery dataset prepared for billing export. | | |
|
||||
| [cicd_repositories](outputs.tf#L110) | CI/CD repository configurations. | | |
|
||||
| [custom_roles](outputs.tf#L122) | Organization-level custom roles. | | |
|
||||
| [federated_identity](outputs.tf#L127) | Workload Identity Federation pool and providers. | | |
|
||||
| [outputs_bucket](outputs.tf#L137) | GCS bucket where generated output files are stored. | | |
|
||||
| [project_ids](outputs.tf#L142) | Projects created by this stage. | | |
|
||||
| [providers](outputs.tf#L152) | 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. | | |
|
||||
| [tfvars](outputs.tf#L168) | Terraform variable files for the following stages. | ✓ | |
|
||||
| [automation](outputs.tf#L102) | Automation resources. | | |
|
||||
| [billing_dataset](outputs.tf#L107) | BigQuery dataset prepared for billing export. | | |
|
||||
| [cicd_repositories](outputs.tf#L112) | CI/CD repository configurations. | | |
|
||||
| [custom_roles](outputs.tf#L124) | Organization-level custom roles. | | |
|
||||
| [federated_identity](outputs.tf#L129) | Workload Identity Federation pool and providers. | | |
|
||||
| [outputs_bucket](outputs.tf#L139) | GCS bucket where generated output files are stored. | | |
|
||||
| [project_ids](outputs.tf#L144) | Projects created by this stage. | | |
|
||||
| [providers](outputs.tf#L154) | Terraform provider files for this stage and dependent stages. | ✓ | <code>stage-01</code> |
|
||||
| [service_accounts](outputs.tf#L161) | Automation service accounts created by this stage. | | |
|
||||
| [tfvars](outputs.tf#L170) | Terraform variable files for the following stages. | ✓ | |
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -16,6 +16,10 @@
|
|||
|
||||
# tfdoc:file:description Automation project and resources.
|
||||
|
||||
locals {
|
||||
cicd_resman_sa = try(module.automation-tf-cicd-sa["resman"].iam_email, "")
|
||||
}
|
||||
|
||||
module "automation-project" {
|
||||
source = "../../../modules/project"
|
||||
billing_account = var.billing_account.id
|
||||
|
@ -151,11 +155,14 @@ module "automation-tf-resman-sa" {
|
|||
prefix = local.prefix
|
||||
# allow SA used by CI/CD workflow to impersonate this SA
|
||||
# we use additive IAM to allow tenant CI/CD SAs to impersonate it
|
||||
iam_additive = {
|
||||
"roles/iam.serviceAccountTokenCreator" = compact([
|
||||
try(module.automation-tf-cicd-sa["resman"].iam_email, null)
|
||||
])
|
||||
}
|
||||
iam_bindings_additive = (
|
||||
local.cicd_resman_sa == "" ? {} : {
|
||||
cicd_token_creator = {
|
||||
member = local.cicd_resman_sa
|
||||
role = "roles/iam.serviceAccountTokenCreator"
|
||||
}
|
||||
}
|
||||
)
|
||||
iam_storage_roles = {
|
||||
(module.automation-tf-output-gcs.name) = ["roles/storage.admin"]
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@ locals {
|
|||
cicd_providers = {
|
||||
for k, v in google_iam_workload_identity_pool_provider.default :
|
||||
k => {
|
||||
audience = try(
|
||||
v.oidc[0].allowed_audiences[0],
|
||||
"https://iam.googleapis.com/${v.name}"
|
||||
audiences = concat(
|
||||
v.oidc[0].allowed_audiences,
|
||||
["https://iam.googleapis.com/${v.name}"]
|
||||
)
|
||||
issuer = local.identity_providers[k].issuer
|
||||
issuer_uri = try(v.oidc[0].issuer_uri, null)
|
||||
|
@ -39,10 +39,15 @@ locals {
|
|||
(
|
||||
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 = {
|
||||
|
|
|
@ -82,7 +82,7 @@ resource "google_iam_workload_identity_pool_provider" "default" {
|
|||
attribute_mapping = each.value.attribute_mapping
|
||||
oidc {
|
||||
# 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.
|
||||
issuer_uri = (
|
||||
each.value.custom_settings.issuer_uri != null
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,111 +17,51 @@
|
|||
# tfdoc:file:description Organization-level IAM.
|
||||
|
||||
locals {
|
||||
# organization authoritative IAM bindings, in an easy to edit format before
|
||||
# they are combined with var.iam a bit further in locals
|
||||
_iam = {
|
||||
"roles/billing.creator" = []
|
||||
"roles/browser" = [
|
||||
"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(
|
||||
# reassemble logical bindings into the formats expected by the module
|
||||
_iam_bindings = merge(
|
||||
local.iam_domain_bindings,
|
||||
local.iam_sa_bindings,
|
||||
local.iam_user_bootstrap_bindings,
|
||||
{
|
||||
"roles/accesscontextmanager.policyAdmin" = [
|
||||
local.groups_iam.gcp-security-admins
|
||||
]
|
||||
"roles/compute.orgFirewallPolicyAdmin" = [
|
||||
local.groups_iam.gcp-network-admins
|
||||
]
|
||||
"roles/compute.xpnAdmin" = [
|
||||
local.groups_iam.gcp-network-admins
|
||||
]
|
||||
# use additive to support cross-org roles for billing
|
||||
"roles/iam.organizationRoleAdmin" = [
|
||||
# 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
|
||||
]
|
||||
"roles/orgpolicy.policyAdmin" = [
|
||||
local.groups_iam.gcp-organization-admins,
|
||||
local.groups_iam.gcp-security-admins,
|
||||
module.automation-tf-resman-sa.iam_email
|
||||
]
|
||||
# the following is useful if roles/browser is not desirable
|
||||
# "roles/resourcemanager.organizationViewer" = [
|
||||
# "domain:${var.organization.domain}"
|
||||
# ]
|
||||
for k, v in local.iam_group_bindings : "group:${k}" => {
|
||||
authoritative = []
|
||||
additive = v.additive
|
||||
}
|
||||
}
|
||||
)
|
||||
_iam_bindings_auth = flatten([
|
||||
for member, data in local._iam_bindings : [
|
||||
for role in data.authoritative : {
|
||||
member = member
|
||||
role = role
|
||||
}
|
||||
]
|
||||
])
|
||||
_iam_bindings_add = flatten([
|
||||
for member, data in local._iam_bindings : [
|
||||
for role in data.additive : {
|
||||
member = member
|
||||
role = role
|
||||
}
|
||||
]
|
||||
])
|
||||
group_iam = {
|
||||
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" = [
|
||||
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
|
||||
]
|
||||
} : {}
|
||||
{
|
||||
for b in local._iam_bindings_auth : b.role => b.member...
|
||||
}
|
||||
)
|
||||
_iam_bootstrap_user = (
|
||||
var.bootstrap_user == null ? [] : ["user:${var.bootstrap_user}"]
|
||||
)
|
||||
iam = {
|
||||
for role in local.iam_roles : role => distinct(concat(
|
||||
try(sort(local._iam[role]), []),
|
||||
try(sort(var.iam[role]), [])
|
||||
))
|
||||
iam_bindings_additive = {
|
||||
for b in local._iam_bindings_add : "${b.role}-${b.member}" => {
|
||||
member = b.member
|
||||
role = b.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" {
|
||||
|
@ -129,40 +69,52 @@ module "organization" {
|
|||
organization_id = "organizations/${var.organization.id}"
|
||||
# human (groups) IAM bindings
|
||||
group_iam = {
|
||||
(local.groups.gcp-organization-admins) = [
|
||||
"roles/cloudasset.owner",
|
||||
"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",
|
||||
]
|
||||
for k, v in local.group_iam :
|
||||
k => distinct(concat(v, lookup(var.group_iam, k, [])))
|
||||
}
|
||||
# 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
|
||||
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, {
|
||||
# this is needed for use in additive IAM bindings, to avoid conflicts
|
||||
(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]
|
||||
}
|
||||
|
|
|
@ -22,7 +22,9 @@ locals {
|
|||
"${path.module}/templates/workflow-${v.type}.yaml", {
|
||||
# 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}
|
||||
audience = local.cicd_providers[v["identity_provider"]].audience
|
||||
audiences = try(
|
||||
local.cicd_providers[v["identity_provider"]].audiences, ""
|
||||
)
|
||||
identity_provider = try(
|
||||
local.cicd_providers[v["identity_provider"]].name, ""
|
||||
)
|
||||
|
|
|
@ -20,13 +20,14 @@ default:
|
|||
- "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
|
||||
variables:
|
||||
AUDIENCE: ${audience}
|
||||
GOOGLE_CREDENTIALS: cicd-sa-credentials.json
|
||||
FAST_OUTPUTS_BUCKET: ${outputs_bucket}
|
||||
FAST_SERVICE_ACCOUNT: ${service_account}
|
||||
FAST_WIF_PROVIDER: ${identity_provider}
|
||||
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
%{~ if tf_providers_file != "" ~}
|
||||
TF_PROVIDERS_FILE: ${tf_providers_file}
|
||||
%{~ endif ~}
|
||||
TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)}
|
||||
|
||||
stages:
|
||||
|
@ -38,16 +39,27 @@ stages:
|
|||
cache:
|
||||
key: gcp-auth
|
||||
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:
|
||||
stage: gcp-auth
|
||||
id_tokens:
|
||||
GITLAB_TOKEN:
|
||||
aud: "$${AUDIENCE}"
|
||||
aud:
|
||||
%{~ for aud in audiences ~}
|
||||
- ${aud}
|
||||
%{~ endfor ~}
|
||||
image:
|
||||
name: google/cloud-sdk:slim
|
||||
stage: gcp-auth
|
||||
script:
|
||||
- echo "$${GITLAB_TOKEN}" > token.txt
|
||||
- |
|
||||
gcloud iam workload-identity-pools create-cred-config \
|
||||
$${FAST_WIF_PROVIDER} \
|
||||
|
@ -55,30 +67,27 @@ gcp-auth:
|
|||
--service-account-token-lifetime-seconds=3600 \
|
||||
--output-file=$${GOOGLE_CREDENTIALS} \
|
||||
--credential-source-file=token.txt
|
||||
- rm token.txt
|
||||
artifacts:
|
||||
untracked: true
|
||||
|
||||
tf-files:
|
||||
stage: tf-files
|
||||
dependencies:
|
||||
- gcp-auth
|
||||
image:
|
||||
name: google/cloud-sdk:slim
|
||||
stage: tf-files
|
||||
script:
|
||||
# - gcloud components install -q alpha
|
||||
- gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS}
|
||||
- mkdir -p .tf-setup
|
||||
- |
|
||||
gcloud alpha storage cp -r \
|
||||
"gs://$${FAST_OUTPUTS_BUCKET}/providers/$${TF_PROVIDERS_FILE}" .tf-setup/
|
||||
- |
|
||||
gcloud alpha storage cp -r \
|
||||
"gs://$${FAST_OUTPUTS_BUCKET}/tfvars" .tf-setup/
|
||||
artifacts:
|
||||
untracked: true
|
||||
dependencies:
|
||||
- gcp-auth
|
||||
%{~ if tf_providers_file != "" ~}
|
||||
- gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/providers/${tf_providers_file}" ./
|
||||
%{~ endif ~}
|
||||
%{~ for f in tf_var_files ~}
|
||||
- gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/tfvars/${f}" ./
|
||||
%{~ endfor ~}
|
||||
- ls -l
|
||||
|
||||
tf-plan:
|
||||
dependencies:
|
||||
- tf-files
|
||||
stage: tf-plan
|
||||
# uncomment the following lines and set the SSH key secret for private modules repo
|
||||
# before_script:
|
||||
|
@ -89,20 +98,13 @@ tf-plan:
|
|||
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
|
||||
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
|
||||
script:
|
||||
- cp ".tf-setup/$${TF_PROVIDERS_FILE}" ./
|
||||
- |
|
||||
for f in "$${TF_VAR_FILES}"; do
|
||||
ln -s ".tf-setup/tfvars/$f" ./
|
||||
done
|
||||
- terraform init
|
||||
- terraform validate
|
||||
- terraform plan
|
||||
artifacts:
|
||||
untracked: true
|
||||
dependencies:
|
||||
- tf-files
|
||||
|
||||
tf-apply:
|
||||
dependencies:
|
||||
- tf-files
|
||||
stage: tf-apply
|
||||
# uncomment the following lines and set the SSH key secret for private modules repo
|
||||
# before_script:
|
||||
|
@ -113,18 +115,9 @@ tf-apply:
|
|||
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
|
||||
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
|
||||
script:
|
||||
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
|
||||
- |
|
||||
for f in $${TF_VAR_FILES}; do
|
||||
ln -s ".tf-setup/tfvars/$f" ./
|
||||
done
|
||||
- terraform init
|
||||
- terraform validate
|
||||
- terraform apply -input=false -auto-approve
|
||||
artifacts:
|
||||
untracked: true
|
||||
dependencies:
|
||||
- tf-files
|
||||
when: manual
|
||||
only:
|
||||
variables:
|
||||
|
|
|
@ -116,8 +116,8 @@ variable "federated_identity_providers" {
|
|||
attribute_condition = optional(string)
|
||||
issuer = string
|
||||
custom_settings = optional(object({
|
||||
issuer_uri = optional(string)
|
||||
allowed_audiences = optional(list(string), [])
|
||||
issuer_uri = optional(string)
|
||||
audiences = optional(list(string), [])
|
||||
}), {})
|
||||
}))
|
||||
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" {
|
||||
# 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."
|
||||
|
@ -150,30 +158,35 @@ variable "groups" {
|
|||
variable "iam" {
|
||||
description = "Organization-level custom IAM settings in role => [principal] format."
|
||||
type = map(list(string))
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "iam_additive" {
|
||||
description = "Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings."
|
||||
type = map(list(string))
|
||||
default = {}
|
||||
variable "iam_bindings_additive" {
|
||||
description = "Organization-level custom additive IAM bindings. Keys are arbitrary."
|
||||
type = map(object({
|
||||
member = string
|
||||
role = string
|
||||
condition = optional(object({
|
||||
expression = string
|
||||
title = string
|
||||
description = optional(string)
|
||||
}))
|
||||
}))
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "locations" {
|
||||
description = "Optional locations for GCS, BigQuery, and logging buckets created here."
|
||||
type = object({
|
||||
bq = string
|
||||
gcs = string
|
||||
logging = string
|
||||
pubsub = list(string)
|
||||
bq = optional(string, "EU")
|
||||
gcs = optional(string, "EU")
|
||||
logging = optional(string, "global")
|
||||
pubsub = optional(list(string), [])
|
||||
})
|
||||
default = {
|
||||
bq = "EU"
|
||||
gcs = "EU"
|
||||
logging = "global"
|
||||
pubsub = []
|
||||
}
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
# See https://cloud.google.com/architecture/exporting-stackdriver-logging-for-security-and-access-analytics
|
||||
|
|
|
@ -342,11 +342,11 @@ 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-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-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-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-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-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> | |
|
||||
|
@ -354,6 +354,7 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
|
|||
| [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. | | |
|
||||
| [organization-iam.tf](./organization-iam.tf) | Organization-level IAM bindings locals. | | |
|
||||
| [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-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | <code>google_storage_bucket_object</code> |
|
||||
|
@ -365,7 +366,7 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
|
|||
|
||||
| name | description | type | required | default | producer |
|
||||
|---|---|:---:|:---:|:---:|:---:|
|
||||
| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | <code title="object({ outputs_bucket = string project_id = string project_number = string federated_identity_pool = string federated_identity_providers = map(object({ audience = string issuer = string issuer_uri = string name = string principal_tpl = string principalset_tpl = string })) })">object({…})</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | <code title="object({ outputs_bucket = string project_id = string project_number = string federated_identity_pool = string federated_identity_providers = map(object({ audiences = list(string) issuer = string issuer_uri = string name = string principal_tpl = string principalset_tpl = string })) })">object({…})</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({ id = string is_org_level = optional(bool, true) no_iam = optional(bool, false) })">object({…})</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [organization](variables.tf#L192) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</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> |
|
||||
|
|
|
@ -79,33 +79,3 @@ module "branch-pf-prod-gcs" {
|
|||
"roles/storage.objectAdmin" = [module.branch-pf-prod-sa.0.iam_email]
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_organization_iam_member" "org_policy_admin_pf_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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,27 +23,6 @@ locals {
|
|||
module.tenant-self-iac-sa[k].iam_email
|
||||
]
|
||||
}
|
||||
tenant_org_iam = compact(flatten([
|
||||
for k, v in var.tenants : [
|
||||
"group:${v.admin_group_email}",
|
||||
v.organization != null ? "domain:${v.organization.domain}" : null
|
||||
]
|
||||
]))
|
||||
}
|
||||
|
||||
|
||||
# org-level roles for each tenant (additive)
|
||||
|
||||
module "tenant-org-iam" {
|
||||
source = "../../../modules/organization"
|
||||
organization_id = "organizations/${var.organization.id}"
|
||||
iam_additive = {
|
||||
"roles/compute.osLoginExternalUser" = [
|
||||
for k, v in var.tenants :
|
||||
"domain:${v.organization.domain}" if v.organization != null
|
||||
]
|
||||
"roles/resourcemanager.organizationViewer" = local.tenant_org_iam
|
||||
}
|
||||
}
|
||||
|
||||
# top-level "Tenants" folder
|
||||
|
@ -107,7 +86,9 @@ module "tenant-core-folder-iam" {
|
|||
folder_create = false
|
||||
iam = merge(
|
||||
{
|
||||
"roles/owner" = [module.tenant-core-sa[each.key].iam_email]
|
||||
"roles/owner" = [
|
||||
module.tenant-core-sa[each.key].iam_email
|
||||
]
|
||||
"roles/viewer" = local.tenant_iam[each.key]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
/**
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
# tfdoc:file:description Organization-level IAM bindings locals.
|
||||
|
||||
locals {
|
||||
iam_bindings_additive = merge(
|
||||
# network and security
|
||||
{
|
||||
sa_net_fw_policy_admin = {
|
||||
member = module.branch-network-sa.iam_email
|
||||
role = "roles/compute.orgFirewallPolicyAdmin"
|
||||
}
|
||||
sa_net_xpn_admin = {
|
||||
member = module.branch-network-sa.iam_email
|
||||
role = "roles/compute.xpnAdmin"
|
||||
}
|
||||
sa_sec_vpcsc_admin = {
|
||||
member = module.branch-security-sa.iam_email
|
||||
role = "roles/accesscontextmanager.policyAdmin"
|
||||
}
|
||||
},
|
||||
# optional billing roles for network and security
|
||||
local.billing_mode != "org" ? {} : {
|
||||
sa_net_billing = {
|
||||
member = module.branch-network-sa.iam_email
|
||||
role = "roles/billing.user"
|
||||
}
|
||||
sa_sec_billing = {
|
||||
member = module.branch-security-sa.iam_email
|
||||
role = "roles/billing.user"
|
||||
}
|
||||
},
|
||||
# optional billing roles for data platform
|
||||
local.billing_mode != "org" || !var.fast_features.data_platform ? {} : {
|
||||
sa_dp_dev_billing = {
|
||||
member = module.branch-dp-dev-sa.0.iam_email
|
||||
role = "roles/billing.user"
|
||||
}
|
||||
sa_dp_prod_billing = {
|
||||
member = module.branch-dp-prod-sa.0.iam_email
|
||||
role = "roles/billing.user"
|
||||
}
|
||||
},
|
||||
# optional billing roles for GKE
|
||||
local.billing_mode != "org" || !var.fast_features.gke ? {} : {
|
||||
sa_gke_dev_billing = {
|
||||
member = module.branch-gke-dev-sa.0.iam_email
|
||||
role = "roles/billing.user"
|
||||
}
|
||||
sa_gke_prod_billing = {
|
||||
member = module.branch-gke-prod-sa.0.iam_email
|
||||
role = "roles/billing.user"
|
||||
}
|
||||
},
|
||||
# optional billing roles for project factory
|
||||
local.billing_mode != "org" || !var.fast_features.project_factory ? {} : {
|
||||
sa_pf_dev_billing = {
|
||||
member = module.branch-pf-dev-sa.0.iam_email
|
||||
role = "roles/billing.user"
|
||||
}
|
||||
sa_pf_dev_costs_manager = {
|
||||
member = module.branch-pf-dev-sa.0.iam_email
|
||||
role = "roles/billing.costsManager"
|
||||
}
|
||||
sa_pf_prod_billing = {
|
||||
member = module.branch-pf-prod-sa.0.iam_email
|
||||
role = "roles/billing.user"
|
||||
}
|
||||
sa_pf_prod_costs_manager = {
|
||||
member = module.branch-pf-prod-sa.0.iam_email
|
||||
role = "roles/billing.costsManager"
|
||||
}
|
||||
},
|
||||
# scoped org policy admin grants for project factory
|
||||
!var.fast_features.project_factory ? {} : {
|
||||
sa_pf_dev_conditional_org_policy = {
|
||||
member = module.branch-pf-dev-sa.0.iam_email
|
||||
role = "roles/orgpolicy.policyAdmin"
|
||||
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
|
||||
}
|
||||
}
|
||||
sa_pf_prod_conditional_org_policy = {
|
||||
member = module.branch-pf-prod-sa.0.iam_email
|
||||
role = "roles/orgpolicy.policyAdmin"
|
||||
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
|
||||
}
|
||||
}
|
||||
},
|
||||
# lightweight tenant roles
|
||||
{
|
||||
for k, v in var.tenants : "oslogin_ext_user-tenant_${k}" => {
|
||||
member = "domain:${v.organization.domain}"
|
||||
role = "roles/compute.osLoginExternalUser"
|
||||
} if v.organization != null
|
||||
},
|
||||
{
|
||||
for k, v in var.tenants : "org-viewer-tenant_${k}_domain" => {
|
||||
member = "domain:${v.organization.domain}"
|
||||
role = "roles/resourcemanager.organizationViewer"
|
||||
} if v.organization != null
|
||||
},
|
||||
{
|
||||
for k, v in var.tenants : "org-viewer-tenant_${k}_admin" => {
|
||||
member = "group:${v.admin_group_email}"
|
||||
role = "roles/resourcemanager.organizationViewer"
|
||||
}
|
||||
},
|
||||
local.billing_mode != "org" ? {} : {
|
||||
for k, v in var.tenants : "billing_user-tenant_${k}_billing_admin" => {
|
||||
member = "group:${v.admin_group_email}"
|
||||
role = "roles/billing.user"
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
|
@ -49,39 +49,8 @@ locals {
|
|||
module "organization" {
|
||||
source = "../../../modules/organization"
|
||||
organization_id = "organizations/${var.organization.id}"
|
||||
# IAM additive bindings, granted via the restricted Organization Admin custom
|
||||
# role assigned in stage 00; they need to be additive to avoid conflicts
|
||||
iam_additive = merge(
|
||||
{
|
||||
"roles/accesscontextmanager.policyAdmin" = [
|
||||
module.branch-security-sa.iam_email
|
||||
]
|
||||
"roles/compute.orgFirewallPolicyAdmin" = [
|
||||
module.branch-network-sa.iam_email
|
||||
]
|
||||
"roles/compute.xpnAdmin" = [
|
||||
module.branch-network-sa.iam_email
|
||||
]
|
||||
},
|
||||
local.billing_mode == "org" ? {
|
||||
"roles/billing.costsManager" = concat(
|
||||
local.branch_optional_sa_lists.pf-dev,
|
||||
local.branch_optional_sa_lists.pf-prod
|
||||
)
|
||||
"roles/billing.user" = concat(
|
||||
[
|
||||
module.branch-network-sa.iam_email,
|
||||
module.branch-security-sa.iam_email,
|
||||
],
|
||||
local.branch_optional_sa_lists.dp-dev,
|
||||
local.branch_optional_sa_lists.dp-prod,
|
||||
local.branch_optional_sa_lists.gke-dev,
|
||||
local.branch_optional_sa_lists.gke-prod,
|
||||
local.branch_optional_sa_lists.pf-dev,
|
||||
local.branch_optional_sa_lists.pf-prod,
|
||||
)
|
||||
} : {}
|
||||
)
|
||||
# additive bindings via delegated IAM grant set in stage 0
|
||||
iam_bindings_additive = local.iam_bindings_additive
|
||||
# sample subset of useful organization policies, edit to suit requirements
|
||||
org_policies = {
|
||||
"iam.allowedPolicyMemberDomains" = {
|
||||
|
@ -116,53 +85,48 @@ module "organization" {
|
|||
org_policies_data_path = "${var.data_dir}/org-policies"
|
||||
# do not assign tagViewer or tagUser roles here on tag keys and values as
|
||||
# they are managed authoritatively and will break multitenant stages
|
||||
tags = merge(
|
||||
local.tags,
|
||||
{
|
||||
(var.tag_names.context) = {
|
||||
description = "Resource management context."
|
||||
iam = {}
|
||||
values = {
|
||||
data = null
|
||||
gke = null
|
||||
networking = null
|
||||
sandbox = null
|
||||
security = null
|
||||
teams = null
|
||||
tenant = null
|
||||
}
|
||||
tags = merge(local.tags, {
|
||||
(var.tag_names.context) = {
|
||||
description = "Resource management context."
|
||||
iam = {}
|
||||
values = {
|
||||
data = null
|
||||
gke = null
|
||||
networking = null
|
||||
sandbox = null
|
||||
security = null
|
||||
teams = null
|
||||
tenant = null
|
||||
}
|
||||
(var.tag_names.environment) = {
|
||||
description = "Environment definition."
|
||||
iam = {}
|
||||
values = {
|
||||
development = null
|
||||
production = null
|
||||
}
|
||||
}
|
||||
(var.tag_names.environment) = {
|
||||
description = "Environment definition."
|
||||
iam = {}
|
||||
values = {
|
||||
development = null
|
||||
production = null
|
||||
}
|
||||
(var.tag_names.org-policies) = {
|
||||
description = "Organization policy conditions."
|
||||
iam = {}
|
||||
values = {
|
||||
allowed-policy-member-domains-all = merge({}, try(
|
||||
local.tags[var.tag_names.org-policies].values.allowed-policy-member-domains-all,
|
||||
{}
|
||||
))
|
||||
}
|
||||
}
|
||||
(var.tag_names.org-policies) = {
|
||||
description = "Organization policy conditions."
|
||||
iam = {}
|
||||
values = {
|
||||
allowed-policy-member-domains-all = merge({}, try(
|
||||
local.tags[var.tag_names.org-policies].values.allowed-policy-member-domains-all,
|
||||
{}
|
||||
))
|
||||
}
|
||||
(var.tag_names.tenant) = {
|
||||
description = "Organization tenant."
|
||||
values = {
|
||||
for k, v in var.tenants : k => {
|
||||
description = v.descriptive_name
|
||||
iam = {
|
||||
"roles/resourcemanager.tagViewer" = local.tenant_iam[k]
|
||||
}
|
||||
}
|
||||
(var.tag_names.tenant) = {
|
||||
description = "Organization tenant."
|
||||
values = {
|
||||
for k, v in var.tenants : k => {
|
||||
description = v.descriptive_name
|
||||
iam = {
|
||||
"roles/resourcemanager.tagViewer" = local.tenant_iam[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
# organization policy conditional roles are in the relevant branch files
|
||||
|
|
|
@ -62,8 +62,8 @@ locals {
|
|||
for k, v in local.cicd_repositories : k => templatefile(
|
||||
"${path.module}/templates/workflow-${v.type}.yaml",
|
||||
merge(local.cicd_workflow_attrs[k], {
|
||||
audience = try(
|
||||
local.identity_providers[v.identity_provider].audience, null
|
||||
audiences = try(
|
||||
local.identity_providers[v.identity_provider].audiences, null
|
||||
)
|
||||
identity_provider = try(
|
||||
local.identity_providers[v.identity_provider].name, null
|
||||
|
|
|
@ -20,13 +20,14 @@ default:
|
|||
- "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
|
||||
variables:
|
||||
AUDIENCE: ${audience}
|
||||
GOOGLE_CREDENTIALS: cicd-sa-credentials.json
|
||||
FAST_OUTPUTS_BUCKET: ${outputs_bucket}
|
||||
FAST_SERVICE_ACCOUNT: ${service_account}
|
||||
FAST_WIF_PROVIDER: ${identity_provider}
|
||||
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
|
||||
%{~ if tf_providers_file != "" ~}
|
||||
TF_PROVIDERS_FILE: ${tf_providers_file}
|
||||
%{~ endif ~}
|
||||
TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)}
|
||||
|
||||
stages:
|
||||
|
@ -38,16 +39,27 @@ stages:
|
|||
cache:
|
||||
key: gcp-auth
|
||||
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:
|
||||
stage: gcp-auth
|
||||
id_tokens:
|
||||
GITLAB_TOKEN:
|
||||
aud: "$${AUDIENCE}"
|
||||
aud:
|
||||
%{~ for aud in audiences ~}
|
||||
- ${aud}
|
||||
%{~ endfor ~}
|
||||
image:
|
||||
name: google/cloud-sdk:slim
|
||||
stage: gcp-auth
|
||||
script:
|
||||
- echo "$${GITLAB_TOKEN}" > token.txt
|
||||
- |
|
||||
gcloud iam workload-identity-pools create-cred-config \
|
||||
$${FAST_WIF_PROVIDER} \
|
||||
|
@ -55,30 +67,27 @@ gcp-auth:
|
|||
--service-account-token-lifetime-seconds=3600 \
|
||||
--output-file=$${GOOGLE_CREDENTIALS} \
|
||||
--credential-source-file=token.txt
|
||||
- rm token.txt
|
||||
artifacts:
|
||||
untracked: true
|
||||
|
||||
tf-files:
|
||||
stage: tf-files
|
||||
dependencies:
|
||||
- gcp-auth
|
||||
image:
|
||||
name: google/cloud-sdk:slim
|
||||
stage: tf-files
|
||||
script:
|
||||
# - gcloud components install -q alpha
|
||||
- gcloud config set auth/credential_file_override $${GOOGLE_CREDENTIALS}
|
||||
- mkdir -p .tf-setup
|
||||
- |
|
||||
gcloud alpha storage cp -r \
|
||||
"gs://$${FAST_OUTPUTS_BUCKET}/providers/$${TF_PROVIDERS_FILE}" .tf-setup/
|
||||
- |
|
||||
gcloud alpha storage cp -r \
|
||||
"gs://$${FAST_OUTPUTS_BUCKET}/tfvars" .tf-setup/
|
||||
artifacts:
|
||||
untracked: true
|
||||
dependencies:
|
||||
- gcp-auth
|
||||
%{~ if tf_providers_file != "" ~}
|
||||
- gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/providers/${tf_providers_file}" ./
|
||||
%{~ endif ~}
|
||||
%{~ for f in tf_var_files ~}
|
||||
- gcloud alpha storage cp -r "gs://$${FAST_OUTPUTS_BUCKET}/tfvars/${f}" ./
|
||||
%{~ endfor ~}
|
||||
- ls -l
|
||||
|
||||
tf-plan:
|
||||
dependencies:
|
||||
- tf-files
|
||||
stage: tf-plan
|
||||
# uncomment the following lines and set the SSH key secret for private modules repo
|
||||
# before_script:
|
||||
|
@ -89,20 +98,13 @@ tf-plan:
|
|||
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
|
||||
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
|
||||
script:
|
||||
- cp ".tf-setup/$${TF_PROVIDERS_FILE}" ./
|
||||
- |
|
||||
for f in "$${TF_VAR_FILES}"; do
|
||||
ln -s ".tf-setup/tfvars/$f" ./
|
||||
done
|
||||
- terraform init
|
||||
- terraform validate
|
||||
- terraform plan
|
||||
artifacts:
|
||||
untracked: true
|
||||
dependencies:
|
||||
- tf-files
|
||||
|
||||
tf-apply:
|
||||
dependencies:
|
||||
- tf-files
|
||||
stage: tf-apply
|
||||
# uncomment the following lines and set the SSH key secret for private modules repo
|
||||
# before_script:
|
||||
|
@ -113,18 +115,9 @@ tf-apply:
|
|||
# ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts
|
||||
# ssh-keyscan gitlab.com | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts
|
||||
script:
|
||||
- cp .tf-setup/$${TF_PROVIDERS_FILE} ./
|
||||
- |
|
||||
for f in $${TF_VAR_FILES}; do
|
||||
ln -s ".tf-setup/tfvars/$f" ./
|
||||
done
|
||||
- terraform init
|
||||
- terraform validate
|
||||
- terraform apply -input=false -auto-approve
|
||||
artifacts:
|
||||
untracked: true
|
||||
dependencies:
|
||||
- tf-files
|
||||
when: manual
|
||||
only:
|
||||
variables:
|
||||
|
|
|
@ -26,7 +26,7 @@ variable "automation" {
|
|||
project_number = string
|
||||
federated_identity_pool = string
|
||||
federated_identity_providers = map(object({
|
||||
audience = string
|
||||
audiences = list(string)
|
||||
issuer = string
|
||||
issuer_uri = string
|
||||
name = string
|
||||
|
|
|
@ -83,7 +83,7 @@ By default, the design assumes the following:
|
|||
- cross-environment traffic and traffic from any untrusted network to any trusted network (and vice versa) pass through the NVAs. For demo purposes, the current NVA performs simple routing/natting only
|
||||
- any traffic from a trusted network to an untrusted network (e.g. Internet) is natted by the NVAs. Users can configure further exclusions
|
||||
|
||||
The trusted landing VPC acts as a hub: it bridges internal resources with the outside world and it hosts the shared services consumed by the spoke VPCs, connected to the hub thorugh VPC network peerings. Spokes are used to partition the environments. By default:
|
||||
The trusted landing VPC acts as a hub: it bridges internal resources with the outside world and it hosts the shared services consumed by the spoke VPCs, connected to the hub through VPC network peerings. Spokes are used to partition the environments. By default:
|
||||
|
||||
- one spoke VPC hosts the development environment resources
|
||||
- one spoke VPC hosts the production environment resources
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue