CI/CD support for Source Repository and Cloud Build (#669)
* add id to outputs * initial cloud build implementation for stage 0 * comments * stage 0 * stage 1, untested * add support for IAM and CB triggers to source repository module * refactor stage 0 to use sourcerepo module * refactor stage 1 to use sourcerepo module * file descriptions * fix gitlab pipeline
This commit is contained in:
parent
0d670afb7e
commit
44ae2671b0
|
@ -98,7 +98,7 @@ jobs:
|
||||||
name: Terraform plan
|
name: Terraform plan
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
terraform plan -out ../plan.out -no-color
|
terraform plan -input=false -out ../plan.out -no-color
|
||||||
|
|
||||||
- id: tf-apply
|
- id: tf-apply
|
||||||
if: github.event.pull_request.merged == true
|
if: github.event.pull_request.merged == true
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
# Copyright 2022 Google LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: alpine:3
|
||||||
|
id: tf-download
|
||||||
|
entrypoint: sh
|
||||||
|
args:
|
||||||
|
- -eEuo
|
||||||
|
- pipefail
|
||||||
|
- -c
|
||||||
|
- |-
|
||||||
|
mkdir -p /builder/home/.local/bin
|
||||||
|
wget https://releases.hashicorp.com/terraform/$${_TF_VERSION}/terraform_$${_TF_VERSION}_linux_amd64.zip
|
||||||
|
unzip terraform_$${_TF_VERSION}_linux_amd64.zip -d /builder/home/.local/bin
|
||||||
|
rm terraform_$${_TF_VERSION}_linux_amd64.zip
|
||||||
|
chmod 755 /builder/home/.local/bin/terraform
|
||||||
|
- name: alpine:3
|
||||||
|
id: tf-check-format
|
||||||
|
entrypoint: sh
|
||||||
|
args:
|
||||||
|
- -eEuo
|
||||||
|
- pipefail
|
||||||
|
- -c
|
||||||
|
- |-
|
||||||
|
terraform fmt -recursive -check /workspace/
|
||||||
|
- name: gcr.io/google.com/cloudsdktool/cloud-sdk:alpine
|
||||||
|
id: tf-files
|
||||||
|
entrypoint: bash
|
||||||
|
args:
|
||||||
|
- -eEuo
|
||||||
|
- pipefail
|
||||||
|
- -c
|
||||||
|
- |-
|
||||||
|
/google-cloud-sdk/bin/gsutil cp \
|
||||||
|
gs://$${_FAST_OUTPUTS_BUCKET}/providers/$${_TF_PROVIDERS_FILE} ./
|
||||||
|
/google-cloud-sdk/bin/gsutil cp -r \
|
||||||
|
gs://$${_FAST_OUTPUTS_BUCKET}/tfvars ./
|
||||||
|
for f in $${_TF_VAR_FILES}; do
|
||||||
|
ln -s tfvars/$f ./
|
||||||
|
done
|
||||||
|
- name: alpine:3
|
||||||
|
id: tf-init
|
||||||
|
entrypoint: sh
|
||||||
|
args:
|
||||||
|
- -eEuo
|
||||||
|
- pipefail
|
||||||
|
- -c
|
||||||
|
- |-
|
||||||
|
terraform init -no-color
|
||||||
|
- name: alpine:3
|
||||||
|
id: tf-check-validate
|
||||||
|
entrypoint: sh
|
||||||
|
args:
|
||||||
|
- -eEuo
|
||||||
|
- pipefail
|
||||||
|
- -c
|
||||||
|
- |-
|
||||||
|
terraform validate -no-color
|
||||||
|
- name: alpine:3
|
||||||
|
id: tf-plan
|
||||||
|
entrypoint: sh
|
||||||
|
args:
|
||||||
|
- -eEuo
|
||||||
|
- pipefail
|
||||||
|
- -c
|
||||||
|
- |-
|
||||||
|
terraform plan -no-color -input=false -out plan.out
|
||||||
|
# store artifact and ask for approval here if needed
|
||||||
|
- name: alpine:3
|
||||||
|
id: tf-apply
|
||||||
|
entrypoint: sh
|
||||||
|
args:
|
||||||
|
- -eEuo
|
||||||
|
- pipefail
|
||||||
|
- -c
|
||||||
|
- |-
|
||||||
|
terraform apply -no-color -input=false -auto-approve plan.out
|
||||||
|
options:
|
||||||
|
env:
|
||||||
|
- PATH=/usr/local/bin:/usr/bin:/bin:/builder/home/.local/bin
|
||||||
|
logging: CLOUD_LOGGING_ONLY
|
||||||
|
substitutions:
|
||||||
|
_FAST_OUTPUTS_BUCKET: ${outputs_bucket}
|
||||||
|
_TF_PROVIDERS_FILE: ${tf_providers_file}
|
||||||
|
_TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)}
|
||||||
|
_TF_VERSION: 1.1.7
|
|
@ -385,6 +385,8 @@ cicd_repositories = {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `type` attribute can be set to one of the supported repository types: `github`, `gitlab`, or `sourcerepo`.
|
||||||
|
|
||||||
Once the stage is applied the generated output files will contain pre-configured workflow files for each repository, that will use Workload Identity Federation via a dedicated service account for each repository to impersonate the automation service account for the stage.
|
Once the stage is applied the generated output files will contain pre-configured workflow files for each repository, that will use Workload Identity Federation via a dedicated service account for each repository to impersonate the automation service account for the stage.
|
||||||
|
|
||||||
The remaining configuration is manual, as it regards the repositories themselves:
|
The remaining configuration is manual, as it regards the repositories themselves:
|
||||||
|
@ -396,6 +398,10 @@ The remaining configuration is manual, as it regards the repositories themselves
|
||||||
- create a key pair
|
- create a key pair
|
||||||
- create a [deploy key](https://docs.github.com/en/developers/overview/managing-deploy-keys#deploy-keys) in the modules repository with the public key
|
- create a [deploy key](https://docs.github.com/en/developers/overview/managing-deploy-keys#deploy-keys) in the modules repository with the public key
|
||||||
- create a `CICD_MODULES_KEY` secret with the private key in each of the repositories that need to access modules
|
- create a `CICD_MODULES_KEY` secret with the private key in each of the repositories that need to access modules
|
||||||
|
- for Gitlab
|
||||||
|
- TODO
|
||||||
|
- for Source Repositories
|
||||||
|
- assign the reader role to the CI/CD service accounts
|
||||||
- create one repository for each stage
|
- create one repository for each stage
|
||||||
- clone and populate them with the stage source
|
- clone and populate them with the stage source
|
||||||
- edit the modules source to match your modules repository
|
- edit the modules source to match your modules repository
|
||||||
|
@ -405,6 +411,7 @@ The remaining configuration is manual, as it regards the repositories themselves
|
||||||
- copy the generated workflow file for the stage from the GCS output files bucket or from the local clone if enabled
|
- copy the generated workflow file for the stage from the GCS output files bucket or from the local clone if enabled
|
||||||
- for GitHub, place it in a `.github/workflows` folder in the repository root
|
- for GitHub, place it in a `.github/workflows` folder in the repository root
|
||||||
- for Gitlab, rename it to `.gitlab-ci.yml` and place it in the repository root
|
- for Gitlab, rename it to `.gitlab-ci.yml` and place it in the repository root
|
||||||
|
- for Source Repositories, place it in `.cloudbuild/workflow.yaml`
|
||||||
|
|
||||||
<!-- TFDOC OPTS files:1 show_extra:1 -->
|
<!-- TFDOC OPTS files:1 show_extra:1 -->
|
||||||
<!-- BEGIN TFDOC -->
|
<!-- BEGIN TFDOC -->
|
||||||
|
@ -415,7 +422,7 @@ The remaining configuration is manual, as it regards the repositories themselves
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| [automation.tf](./automation.tf) | Automation project and resources. | <code>gcs</code> · <code>iam-service-account</code> · <code>project</code> | |
|
| [automation.tf](./automation.tf) | Automation project and resources. | <code>gcs</code> · <code>iam-service-account</code> · <code>project</code> | |
|
||||||
| [billing.tf](./billing.tf) | Billing export project and dataset. | <code>bigquery-dataset</code> · <code>organization</code> · <code>project</code> | <code>google_billing_account_iam_member</code> · <code>google_organization_iam_binding</code> |
|
| [billing.tf](./billing.tf) | Billing export project and dataset. | <code>bigquery-dataset</code> · <code>organization</code> · <code>project</code> | <code>google_billing_account_iam_member</code> · <code>google_organization_iam_binding</code> |
|
||||||
| [cicd.tf](./cicd.tf) | Workload Identity Federation configurations for CI/CD. | <code>iam-service-account</code> | |
|
| [cicd.tf](./cicd.tf) | Workload Identity Federation configurations for CI/CD. | <code>iam-service-account</code> · <code>source-repository</code> | |
|
||||||
| [identity-providers.tf](./identity-providers.tf) | Workload Identity Federation provider definitions. | | <code>google_iam_workload_identity_pool</code> · <code>google_iam_workload_identity_pool_provider</code> |
|
| [identity-providers.tf](./identity-providers.tf) | Workload Identity Federation provider definitions. | | <code>google_iam_workload_identity_pool</code> · <code>google_iam_workload_identity_pool_provider</code> |
|
||||||
| [log-export.tf](./log-export.tf) | Audit log project and sink. | <code>bigquery-dataset</code> · <code>gcs</code> · <code>logging-bucket</code> · <code>project</code> · <code>pubsub</code> | |
|
| [log-export.tf](./log-export.tf) | Audit log project and sink. | <code>bigquery-dataset</code> · <code>gcs</code> · <code>logging-bucket</code> · <code>project</code> · <code>pubsub</code> | |
|
||||||
| [main.tf](./main.tf) | Module-level locals and resources. | | |
|
| [main.tf](./main.tf) | Module-level locals and resources. | | |
|
||||||
|
@ -430,31 +437,31 @@ The remaining configuration is manual, as it regards the repositories themselves
|
||||||
| name | description | type | required | default | producer |
|
| name | description | type | required | default | producer |
|
||||||
|---|---|:---:|:---:|:---:|:---:|
|
|---|---|:---:|:---:|:---:|:---:|
|
||||||
| [billing_account](variables.tf#L17) | Billing account id and organization id ('nnnnnnnn' or null). | <code title="object({ id = string organization_id = number })">object({…})</code> | ✓ | | |
|
| [billing_account](variables.tf#L17) | Billing account id and organization id ('nnnnnnnn' or null). | <code title="object({ id = string organization_id = number })">object({…})</code> | ✓ | | |
|
||||||
| [organization](variables.tf#L146) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</code> | ✓ | | |
|
| [organization](variables.tf#L152) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</code> | ✓ | | |
|
||||||
| [prefix](variables.tf#L161) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | |
|
| [prefix](variables.tf#L167) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | |
|
||||||
| [bootstrap_user](variables.tf#L25) | Email of the nominal user running this stage for the first time. | <code>string</code> | | <code>null</code> | |
|
| [bootstrap_user](variables.tf#L25) | Email of the nominal user running this stage for the first time. | <code>string</code> | | <code>null</code> | |
|
||||||
| [cicd_repositories](variables.tf#L31) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | <code title="object({ bootstrap = object({ branch = string identity_provider = string name = string type = string }) resman = object({ branch = string identity_provider = string name = string type = string }) })">object({…})</code> | | <code>null</code> | |
|
| [cicd_repositories](variables.tf#L31) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | <code title="object({ bootstrap = object({ branch = string identity_provider = string name = string type = string }) resman = object({ branch = string identity_provider = string name = string type = string }) })">object({…})</code> | | <code>null</code> | |
|
||||||
| [custom_role_names](variables.tf#L71) | Names of custom roles defined at the org level. | <code title="object({ organization_iam_admin = string service_project_network_admin = string })">object({…})</code> | | <code title="{ organization_iam_admin = "organizationIamAdmin" service_project_network_admin = "serviceProjectNetworkAdmin" }">{…}</code> | |
|
| [custom_role_names](variables.tf#L77) | Names of custom roles defined at the org level. | <code title="object({ organization_iam_admin = string service_project_network_admin = string })">object({…})</code> | | <code title="{ organization_iam_admin = "organizationIamAdmin" service_project_network_admin = "serviceProjectNetworkAdmin" }">{…}</code> | |
|
||||||
| [federated_identity_providers](variables.tf#L83) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | <code title="map(object({ attribute_condition = string issuer = string }))">map(object({…}))</code> | | <code>{}</code> | |
|
| [federated_identity_providers](variables.tf#L89) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | <code title="map(object({ attribute_condition = string issuer = string }))">map(object({…}))</code> | | <code>{}</code> | |
|
||||||
| [groups](variables.tf#L93) | Group names to grant organization-level permissions. | <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-support" }">{…}</code> | |
|
| [groups](variables.tf#L99) | Group names to grant organization-level permissions. | <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-support" }">{…}</code> | |
|
||||||
| [iam](variables.tf#L107) | Organization-level custom IAM settings in role => [principal] format. | <code>map(list(string))</code> | | <code>{}</code> | |
|
| [iam](variables.tf#L113) | Organization-level custom IAM settings in role => [principal] format. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||||
| [iam_additive](variables.tf#L113) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | <code>map(list(string))</code> | | <code>{}</code> | |
|
| [iam_additive](variables.tf#L119) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | <code>map(list(string))</code> | | <code>{}</code> | |
|
||||||
| [log_sinks](variables.tf#L121) | 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> | |
|
| [log_sinks](variables.tf#L127) | 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> | |
|
||||||
| [outputs_location](variables.tf#L155) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable | <code>string</code> | | <code>null</code> | |
|
| [outputs_location](variables.tf#L161) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable | <code>string</code> | | <code>null</code> | |
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
|
||||||
| name | description | sensitive | consumers |
|
| name | description | sensitive | consumers |
|
||||||
|---|---|:---:|---|
|
|---|---|:---:|---|
|
||||||
| [automation](outputs.tf#L93) | Automation resources. | | |
|
| [automation](outputs.tf#L81) | Automation resources. | | |
|
||||||
| [billing_dataset](outputs.tf#L98) | BigQuery dataset prepared for billing export. | | |
|
| [billing_dataset](outputs.tf#L86) | BigQuery dataset prepared for billing export. | | |
|
||||||
| [cicd_repositories](outputs.tf#L103) | CI/CD repository configurations. | | |
|
| [cicd_repositories](outputs.tf#L91) | CI/CD repository configurations. | | |
|
||||||
| [custom_roles](outputs.tf#L115) | Organization-level custom roles. | | |
|
| [custom_roles](outputs.tf#L103) | Organization-level custom roles. | | |
|
||||||
| [federated_identity](outputs.tf#L120) | Workload Identity Federation pool and providers. | | |
|
| [federated_identity](outputs.tf#L108) | Workload Identity Federation pool and providers. | | |
|
||||||
| [outputs_bucket](outputs.tf#L130) | GCS bucket where generated output files are stored. | | |
|
| [outputs_bucket](outputs.tf#L118) | GCS bucket where generated output files are stored. | | |
|
||||||
| [project_ids](outputs.tf#L135) | Projects created by this stage. | | |
|
| [project_ids](outputs.tf#L123) | Projects created by this stage. | | |
|
||||||
| [providers](outputs.tf#L154) | Terraform provider files for this stage and dependent stages. | ✓ | <code>stage-01</code> |
|
| [providers](outputs.tf#L142) | Terraform provider files for this stage and dependent stages. | ✓ | <code>stage-01</code> |
|
||||||
| [service_accounts](outputs.tf#L144) | Automation service accounts created by this stage. | | |
|
| [service_accounts](outputs.tf#L132) | Automation service accounts created by this stage. | | |
|
||||||
| [tfvars](outputs.tf#L163) | Terraform variable files for the following stages. | ✓ | |
|
| [tfvars](outputs.tf#L151) | Terraform variable files for the following stages. | ✓ | |
|
||||||
|
|
||||||
<!-- END TFDOC -->
|
<!-- END TFDOC -->
|
||||||
|
|
|
@ -55,6 +55,7 @@ module "automation-project" {
|
||||||
"bigquerystorage.googleapis.com",
|
"bigquerystorage.googleapis.com",
|
||||||
"billingbudgets.googleapis.com",
|
"billingbudgets.googleapis.com",
|
||||||
"cloudbilling.googleapis.com",
|
"cloudbilling.googleapis.com",
|
||||||
|
"cloudbuild.googleapis.com",
|
||||||
"cloudkms.googleapis.com",
|
"cloudkms.googleapis.com",
|
||||||
"cloudresourcemanager.googleapis.com",
|
"cloudresourcemanager.googleapis.com",
|
||||||
"container.googleapis.com",
|
"container.googleapis.com",
|
||||||
|
@ -65,6 +66,7 @@ module "automation-project" {
|
||||||
"pubsub.googleapis.com",
|
"pubsub.googleapis.com",
|
||||||
"servicenetworking.googleapis.com",
|
"servicenetworking.googleapis.com",
|
||||||
"serviceusage.googleapis.com",
|
"serviceusage.googleapis.com",
|
||||||
|
"sourcerepo.googleapis.com",
|
||||||
"stackdriver.googleapis.com",
|
"stackdriver.googleapis.com",
|
||||||
"storage-component.googleapis.com",
|
"storage-component.googleapis.com",
|
||||||
"storage.googleapis.com",
|
"storage.googleapis.com",
|
||||||
|
@ -72,7 +74,7 @@ module "automation-project" {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
# outputt files bucket
|
# output files bucket
|
||||||
|
|
||||||
module "automation-tf-output-gcs" {
|
module "automation-tf-output-gcs" {
|
||||||
source = "../../../modules/gcs"
|
source = "../../../modules/gcs"
|
||||||
|
@ -100,6 +102,7 @@ module "automation-tf-bootstrap-sa" {
|
||||||
name = "bootstrap-0"
|
name = "bootstrap-0"
|
||||||
description = "Terraform organization bootstrap service account."
|
description = "Terraform organization bootstrap service account."
|
||||||
prefix = local.prefix
|
prefix = local.prefix
|
||||||
|
# allow SA used by CI/CD workflow to impersonate this SA
|
||||||
iam = {
|
iam = {
|
||||||
"roles/iam.serviceAccountTokenCreator" = compact([
|
"roles/iam.serviceAccountTokenCreator" = compact([
|
||||||
try(module.automation-tf-cicd-sa["bootstrap"].iam_email, null)
|
try(module.automation-tf-cicd-sa["bootstrap"].iam_email, null)
|
||||||
|
@ -130,6 +133,7 @@ module "automation-tf-resman-sa" {
|
||||||
name = "resman-0"
|
name = "resman-0"
|
||||||
description = "Terraform stage 1 resman service account."
|
description = "Terraform stage 1 resman service account."
|
||||||
prefix = local.prefix
|
prefix = local.prefix
|
||||||
|
# allow SA used by CI/CD workflow to impersonate this SA
|
||||||
iam = {
|
iam = {
|
||||||
"roles/iam.serviceAccountTokenCreator" = compact([
|
"roles/iam.serviceAccountTokenCreator" = compact([
|
||||||
try(module.automation-tf-cicd-sa["resman"].iam_email, null)
|
try(module.automation-tf-cicd-sa["resman"].iam_email, null)
|
||||||
|
|
|
@ -17,31 +17,83 @@
|
||||||
# tfdoc:file:description Workload Identity Federation configurations for CI/CD.
|
# tfdoc:file:description Workload Identity Federation configurations for CI/CD.
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
# TODO: map null provider to Cloud Build once we add support for it
|
|
||||||
cicd_repositories = {
|
cicd_repositories = {
|
||||||
for k, v in coalesce(var.cicd_repositories, {}) : k => v
|
for k, v in coalesce(var.cicd_repositories, {}) : k => v
|
||||||
if(
|
if(
|
||||||
v != null
|
v != null
|
||||||
&&
|
&&
|
||||||
contains(keys(local.identity_providers), v.identity_provider)
|
(
|
||||||
|
v.type == "sourcerepo"
|
||||||
|
||
|
||||||
|
contains(keys(local.identity_providers), coalesce(v.identity_provider, ":"))
|
||||||
|
)
|
||||||
&&
|
&&
|
||||||
fileexists("${path.module}/templates/workflow-${v.type}.yaml")
|
fileexists("${path.module}/templates/workflow-${v.type}.yaml")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
cicd_service_accounts = {
|
cicd_workflow_providers = {
|
||||||
for k, v in module.automation-tf-cicd-sa :
|
bootstrap = "00-bootstrap-providers.tf"
|
||||||
k => v.iam_email
|
resman = "01-resman-providers.tf"
|
||||||
|
}
|
||||||
|
cicd_workflow_var_files = {
|
||||||
|
bootstrap = []
|
||||||
|
resman = [
|
||||||
|
"00-bootstrap.auto.tfvars.json",
|
||||||
|
"globals.auto.tfvars.json"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# source repository
|
||||||
|
|
||||||
|
module "automation-tf-cicd-repo" {
|
||||||
|
source = "../../../modules/source-repository"
|
||||||
|
for_each = {
|
||||||
|
for k, v in local.cicd_repositories : k => v if v.type == "sourcerepo"
|
||||||
|
}
|
||||||
|
project_id = module.automation-project.project_id
|
||||||
|
name = each.value.name
|
||||||
|
iam = {
|
||||||
|
"roles/source.admin" = [
|
||||||
|
each.key == "bootstrap"
|
||||||
|
? module.automation-tf-bootstrap-sa.iam_email
|
||||||
|
: module.automation-tf-resman-sa.iam_email
|
||||||
|
]
|
||||||
|
"roles/source.reader" = [
|
||||||
|
module.automation-tf-cicd-sa[each.key].iam_email
|
||||||
|
]
|
||||||
|
}
|
||||||
|
triggers = {
|
||||||
|
"fast-00-${each.key}" = {
|
||||||
|
filename = ".cloudbuild/workflow.yaml"
|
||||||
|
included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
|
||||||
|
service_account = module.automation-tf-cicd-sa[each.key].id
|
||||||
|
substitutions = {}
|
||||||
|
template = {
|
||||||
|
project_id = null
|
||||||
|
branch_name = each.value.branch
|
||||||
|
repo_name = each.value.name
|
||||||
|
tag_name = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# SAs used by CI/CD workflows to impersonate automation SAs
|
||||||
|
|
||||||
module "automation-tf-cicd-sa" {
|
module "automation-tf-cicd-sa" {
|
||||||
source = "../../../modules/iam-service-account"
|
source = "../../../modules/iam-service-account"
|
||||||
for_each = local.cicd_repositories
|
for_each = local.cicd_repositories
|
||||||
project_id = module.automation-project.project_id
|
project_id = module.automation-project.project_id
|
||||||
name = "${each.key}-1"
|
name = "${each.key}-1"
|
||||||
description = "Terraform CI/CD stage 1 ${each.key} service account."
|
description = "Terraform CI/CD ${each.key} service account."
|
||||||
prefix = local.prefix
|
prefix = local.prefix
|
||||||
iam = {
|
iam = (
|
||||||
|
each.value.type == "sourcerepo"
|
||||||
|
# used directly from the cloud build trigger for source repos
|
||||||
|
? {}
|
||||||
|
# impersonated via workload identity federation for external repos
|
||||||
|
: {
|
||||||
"roles/iam.workloadIdentityUser" = [
|
"roles/iam.workloadIdentityUser" = [
|
||||||
each.value.branch == null
|
each.value.branch == null
|
||||||
? format(
|
? format(
|
||||||
|
@ -57,6 +109,10 @@ module "automation-tf-cicd-sa" {
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
iam_project_roles = {
|
||||||
|
(module.automation-project.project_id) = ["roles/logging.logWriter"]
|
||||||
|
}
|
||||||
iam_storage_roles = {
|
iam_storage_roles = {
|
||||||
(module.automation-tf-output-gcs.name) = ["roles/storage.objectViewer"]
|
(module.automation-tf-output-gcs.name) = ["roles/storage.objectViewer"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ locals {
|
||||||
principal_tpl = "principal://iam.googleapis.com/%s/subject/repo:%s:ref:refs/heads/%s"
|
principal_tpl = "principal://iam.googleapis.com/%s/subject/repo:%s:ref:refs/heads/%s"
|
||||||
principalset_tpl = "principalSet://iam.googleapis.com/%s/attribute.repository/%s"
|
principalset_tpl = "principalSet://iam.googleapis.com/%s/attribute.repository/%s"
|
||||||
}
|
}
|
||||||
|
# https://docs.gitlab.com/ee/ci/cloud_services/index.html#how-it-works
|
||||||
gitlab = {
|
gitlab = {
|
||||||
attribute_mapping = {
|
attribute_mapping = {
|
||||||
"google.subject" = "assertion.sub"
|
"google.subject" = "assertion.sub"
|
||||||
|
|
|
@ -15,34 +15,22 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
_cicd_workflow_attrs = {
|
|
||||||
bootstrap = {
|
|
||||||
service_account = try(
|
|
||||||
module.automation-tf-cicd-sa["bootstrap"].email, null
|
|
||||||
)
|
|
||||||
tf_providers_file = "00-bootstrap-providers.tf"
|
|
||||||
tf_var_files = []
|
|
||||||
}
|
|
||||||
resman = {
|
|
||||||
service_account = try(
|
|
||||||
module.automation-tf-cicd-sa["resman"].email, null
|
|
||||||
)
|
|
||||||
tf_providers_file = "01-resman-providers.tf"
|
|
||||||
tf_var_files = [
|
|
||||||
"00-bootstrap.auto.tfvars.json",
|
|
||||||
"globals.auto.tfvars.json"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_tpl_providers = "${path.module}/templates/providers.tf.tpl"
|
_tpl_providers = "${path.module}/templates/providers.tf.tpl"
|
||||||
|
# render CI/CD workflow templates
|
||||||
cicd_workflows = {
|
cicd_workflows = {
|
||||||
for k, v in local.cicd_repositories : k => templatefile(
|
for k, v in local.cicd_repositories : k => templatefile(
|
||||||
"${path.module}/templates/workflow-${v.type}.yaml",
|
"${path.module}/templates/workflow-${v.type}.yaml", {
|
||||||
merge(local._cicd_workflow_attrs[k], {
|
identity_provider = try(
|
||||||
identity_provider = local.wif_providers[v["identity_provider"]].name
|
local.wif_providers[v["identity_provider"]].name, ""
|
||||||
|
)
|
||||||
outputs_bucket = module.automation-tf-output-gcs.name
|
outputs_bucket = module.automation-tf-output-gcs.name
|
||||||
|
service_account = try(
|
||||||
|
module.automation-tf-cicd-sa[k].email, ""
|
||||||
|
)
|
||||||
stage_name = k
|
stage_name = k
|
||||||
})
|
tf_providers_file = local.cicd_workflow_providers[k]
|
||||||
|
tf_var_files = local.cicd_workflow_var_files[k]
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
custom_roles = {
|
custom_roles = {
|
||||||
|
@ -106,8 +94,8 @@ output "cicd_repositories" {
|
||||||
for k, v in local.cicd_repositories : k => {
|
for k, v in local.cicd_repositories : k => {
|
||||||
branch = v.branch
|
branch = v.branch
|
||||||
name = v.name
|
name = v.name
|
||||||
provider = local.wif_providers[v.identity_provider].name
|
provider = try(local.wif_providers[v.identity_provider].name, null)
|
||||||
service_account = module.automation-tf-cicd-sa[k].email
|
service_account = try(module.automation-tf-cicd-sa[k].email, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ variable "bootstrap_user" {
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "cicd_repositories" {
|
variable "cicd_repositories" {
|
||||||
# TODO: edit description once we add support for Cloud Build (null provider)
|
|
||||||
description = "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."
|
description = "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."
|
||||||
type = object({
|
type = object({
|
||||||
bootstrap = object({
|
bootstrap = object({
|
||||||
|
@ -49,22 +48,29 @@ variable "cicd_repositories" {
|
||||||
validation {
|
validation {
|
||||||
condition = alltrue([
|
condition = alltrue([
|
||||||
for k, v in coalesce(var.cicd_repositories, {}) :
|
for k, v in coalesce(var.cicd_repositories, {}) :
|
||||||
v == null || (
|
v == null || try(v.name, null) != null
|
||||||
try(v.name, null) != null
|
|
||||||
&&
|
|
||||||
try(v.identity_provider, null) != null
|
|
||||||
)
|
|
||||||
])
|
])
|
||||||
error_message = "Non-null repositories need non-null name and providers."
|
error_message = "Non-null repositories need a non-null name."
|
||||||
}
|
}
|
||||||
validation {
|
validation {
|
||||||
condition = alltrue([
|
condition = alltrue([
|
||||||
for k, v in coalesce(var.cicd_repositories, {}) :
|
for k, v in coalesce(var.cicd_repositories, {}) :
|
||||||
v == null || (
|
v == null || (
|
||||||
contains(["gitlab", "github"], coalesce(try(v.type, null), "null"))
|
try(v.identity_provider, null) != null
|
||||||
|
||
|
||||||
|
try(v.type, null) == "sourcerepo"
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
error_message = "Invalid repository type, supported types: 'github' or 'gitlab'."
|
error_message = "Non-null repositories need a non-null provider unless type is 'sourcerepo'."
|
||||||
|
}
|
||||||
|
validation {
|
||||||
|
condition = alltrue([
|
||||||
|
for k, v in coalesce(var.cicd_repositories, {}) :
|
||||||
|
v == null || (
|
||||||
|
contains(["github", "gitlab", "sourcerepo"], coalesce(try(v.type, null), "null"))
|
||||||
|
)
|
||||||
|
])
|
||||||
|
error_message = "Invalid repository type, supported types: 'github' 'gitlab' or 'sourcerepo'."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -163,6 +163,10 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
|
||||||
| [branch-sandbox.tf](./branch-sandbox.tf) | Sandbox stage resources. | <code>folder</code> · <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> | |
|
||||||
| [branch-security.tf](./branch-security.tf) | Security stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | |
|
| [branch-security.tf](./branch-security.tf) | Security stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | |
|
||||||
| [branch-teams.tf](./branch-teams.tf) | Team stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | |
|
| [branch-teams.tf](./branch-teams.tf) | Team stage resources. | <code>folder</code> · <code>gcs</code> · <code>iam-service-account</code> | |
|
||||||
|
| [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-networking.tf](./cicd-networking.tf) | CI/CD resources for the networking branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
|
||||||
|
| [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
|
||||||
|
| [cicd-teams.tf](./cicd-teams.tf) | CI/CD resources for the teams branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
|
||||||
| [main.tf](./main.tf) | Module-level locals and resources. | | |
|
| [main.tf](./main.tf) | Module-level locals and resources. | | |
|
||||||
| [organization.tf](./organization.tf) | Organization policies. | <code>organization</code> | <code>google_organization_iam_member</code> |
|
| [organization.tf](./organization.tf) | Organization policies. | <code>organization</code> | <code>google_organization_iam_member</code> |
|
||||||
| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | <code>local_file</code> |
|
| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | <code>local_file</code> |
|
||||||
|
@ -176,28 +180,28 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
|
||||||
|---|---|:---:|:---:|:---:|:---:|
|
|---|---|:---:|:---:|:---:|:---:|
|
||||||
| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | <code title="object({ outputs_bucket = string project_id = 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>00-bootstrap</code> |
|
| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | <code title="object({ outputs_bucket = string project_id = 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>00-bootstrap</code> |
|
||||||
| [billing_account](variables.tf#L37) | Billing account id and organization id ('nnnnnnnn' or null). | <code title="object({ id = string organization_id = number })">object({…})</code> | ✓ | | <code>00-bootstrap</code> |
|
| [billing_account](variables.tf#L37) | Billing account id and organization id ('nnnnnnnn' or null). | <code title="object({ id = string organization_id = number })">object({…})</code> | ✓ | | <code>00-bootstrap</code> |
|
||||||
| [organization](variables.tf#L133) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</code> | ✓ | | <code>00-bootstrap</code> |
|
| [organization](variables.tf#L140) | Organization details. | <code title="object({ domain = string id = number customer_id = string })">object({…})</code> | ✓ | | <code>00-bootstrap</code> |
|
||||||
| [prefix](variables.tf#L157) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>00-bootstrap</code> |
|
| [prefix](variables.tf#L164) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>00-bootstrap</code> |
|
||||||
| [cicd_repositories](variables.tf#L46) | 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 }) 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> | |
|
| [cicd_repositories](variables.tf#L46) | 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 }) 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#L109) | 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>00-bootstrap</code> |
|
| [custom_roles](variables.tf#L116) | 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>00-bootstrap</code> |
|
||||||
| [groups](variables.tf#L118) | Group names to grant organization-level permissions. | <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-support" }">{…}</code> | <code>00-bootstrap</code> |
|
| [groups](variables.tf#L125) | Group names to grant organization-level permissions. | <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-support" }">{…}</code> | <code>00-bootstrap</code> |
|
||||||
| [organization_policy_configs](variables.tf#L143) | Organization policies customization. | <code title="object({ allowed_policy_member_domains = list(string) })">object({…})</code> | | <code>null</code> | |
|
| [organization_policy_configs](variables.tf#L150) | Organization policies customization. | <code title="object({ allowed_policy_member_domains = list(string) })">object({…})</code> | | <code>null</code> | |
|
||||||
| [outputs_location](variables.tf#L151) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable | <code>string</code> | | <code>null</code> | |
|
| [outputs_location](variables.tf#L158) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable | <code>string</code> | | <code>null</code> | |
|
||||||
| [tag_names](variables.tf#L168) | Customized names for resource management tags. | <code title="object({ context = string environment = string })">object({…})</code> | | <code title="{ context = "context" environment = "environment" }">{…}</code> | |
|
| [tag_names](variables.tf#L175) | Customized names for resource management tags. | <code title="object({ context = string environment = string })">object({…})</code> | | <code title="{ context = "context" environment = "environment" }">{…}</code> | |
|
||||||
| [team_folders](variables.tf#L185) | 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> | |
|
| [team_folders](variables.tf#L192) | 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> | |
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
|
||||||
| name | description | sensitive | consumers |
|
| name | description | sensitive | consumers |
|
||||||
|---|---|:---:|---|
|
|---|---|:---:|---|
|
||||||
| [cicd_repositories](outputs.tf#L157) | WIF configuration for CI/CD repositories. | | |
|
| [cicd_repositories](outputs.tf#L143) | WIF configuration for CI/CD repositories. | | |
|
||||||
| [dataplatform](outputs.tf#L169) | Data for the Data Platform stage. | | |
|
| [dataplatform](outputs.tf#L155) | Data for the Data Platform stage. | | |
|
||||||
| [networking](outputs.tf#L185) | Data for the networking stage. | | |
|
| [networking](outputs.tf#L171) | Data for the networking stage. | | |
|
||||||
| [project_factories](outputs.tf#L194) | Data for the project factories stage. | | |
|
| [project_factories](outputs.tf#L180) | Data for the project factories stage. | | |
|
||||||
| [providers](outputs.tf#L210) | Terraform provider files for this stage and dependent stages. | ✓ | <code>02-networking</code> · <code>02-security</code> · <code>03-dataplatform</code> · <code>xx-sandbox</code> · <code>xx-teams</code> |
|
| [providers](outputs.tf#L196) | 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#L217) | Data for the sandbox stage. | | <code>xx-sandbox</code> |
|
| [sandbox](outputs.tf#L203) | Data for the sandbox stage. | | <code>xx-sandbox</code> |
|
||||||
| [security](outputs.tf#L227) | Data for the networking stage. | | <code>02-security</code> |
|
| [security](outputs.tf#L213) | Data for the networking stage. | | <code>02-security</code> |
|
||||||
| [teams](outputs.tf#L237) | Data for the teams stage. | | |
|
| [teams](outputs.tf#L223) | Data for the teams stage. | | |
|
||||||
| [tfvars](outputs.tf#L250) | Terraform variable files for the following stages. | ✓ | |
|
| [tfvars](outputs.tf#L236) | Terraform variable files for the following stages. | ✓ | |
|
||||||
|
|
||||||
<!-- END TFDOC -->
|
<!-- END TFDOC -->
|
||||||
|
|
|
@ -122,67 +122,3 @@ module "branch-dp-prod-gcs" {
|
||||||
"roles/storage.objectAdmin" = [module.branch-dp-prod-sa.iam_email]
|
"roles/storage.objectAdmin" = [module.branch-dp-prod-sa.iam_email]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ci/cd service accounts
|
|
||||||
|
|
||||||
module "branch-dp-dev-sa-cicd" {
|
|
||||||
source = "../../../modules/iam-service-account"
|
|
||||||
for_each = (
|
|
||||||
lookup(local.cicd_repositories, "dp_dev", null) == null
|
|
||||||
? {}
|
|
||||||
: { 0 = local.cicd_repositories.dp_dev }
|
|
||||||
)
|
|
||||||
project_id = var.automation.project_id
|
|
||||||
name = "dev-resman-dp-1"
|
|
||||||
description = "Terraform CI/CD data platform development service account."
|
|
||||||
prefix = var.prefix
|
|
||||||
iam = {
|
|
||||||
"roles/iam.workloadIdentityUser" = [
|
|
||||||
each.value.branch == null
|
|
||||||
? format(
|
|
||||||
local.identity_providers[each.value.identity_provider].principalset_tpl,
|
|
||||||
each.value.name
|
|
||||||
)
|
|
||||||
: format(
|
|
||||||
local.identity_providers[each.value.identity_provider].principal_tpl,
|
|
||||||
each.value.name,
|
|
||||||
each.value.branch
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
iam_storage_roles = {
|
|
||||||
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module "branch-dp-prod-sa-cicd" {
|
|
||||||
source = "../../../modules/iam-service-account"
|
|
||||||
for_each = (
|
|
||||||
lookup(local.cicd_repositories, "dp_prod", null) == null
|
|
||||||
? {}
|
|
||||||
: { 0 = local.cicd_repositories.dp_prod }
|
|
||||||
)
|
|
||||||
project_id = var.automation.project_id
|
|
||||||
name = "prod-resman-dp-1"
|
|
||||||
description = "Terraform CI/CD data platform production service account."
|
|
||||||
prefix = var.prefix
|
|
||||||
iam = {
|
|
||||||
"roles/iam.workloadIdentityUser" = [
|
|
||||||
each.value.branch == null
|
|
||||||
? format(
|
|
||||||
local.identity_providers[each.value.identity_provider].principalset_tpl,
|
|
||||||
var.automation.federated_identity_pool,
|
|
||||||
each.value.name
|
|
||||||
)
|
|
||||||
: format(
|
|
||||||
local.identity_providers[each.value.identity_provider].principal_tpl,
|
|
||||||
var.automation.federated_identity_pool,
|
|
||||||
each.value.name,
|
|
||||||
each.value.branch
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
iam_storage_roles = {
|
|
||||||
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -107,37 +107,3 @@ module "branch-network-gcs" {
|
||||||
"roles/storage.objectAdmin" = [module.branch-network-sa.iam_email]
|
"roles/storage.objectAdmin" = [module.branch-network-sa.iam_email]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ci/cd service account
|
|
||||||
|
|
||||||
module "branch-network-sa-cicd" {
|
|
||||||
source = "../../../modules/iam-service-account"
|
|
||||||
for_each = (
|
|
||||||
lookup(local.cicd_repositories, "networking", null) == null
|
|
||||||
? {}
|
|
||||||
: { 0 = local.cicd_repositories.networking }
|
|
||||||
)
|
|
||||||
project_id = var.automation.project_id
|
|
||||||
name = "prod-resman-net-1"
|
|
||||||
description = "Terraform CI/CD stage 2 networking service account."
|
|
||||||
prefix = var.prefix
|
|
||||||
iam = {
|
|
||||||
"roles/iam.workloadIdentityUser" = [
|
|
||||||
each.value.branch == null
|
|
||||||
? format(
|
|
||||||
local.identity_providers[each.value.identity_provider].principalset_tpl,
|
|
||||||
var.automation.federated_identity_pool,
|
|
||||||
each.value.name
|
|
||||||
)
|
|
||||||
: format(
|
|
||||||
local.identity_providers[each.value.identity_provider].principal_tpl,
|
|
||||||
var.automation.federated_identity_pool,
|
|
||||||
each.value.name,
|
|
||||||
each.value.branch
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
iam_storage_roles = {
|
|
||||||
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -74,37 +74,3 @@ module "branch-security-gcs" {
|
||||||
"roles/storage.objectAdmin" = [module.branch-security-sa.iam_email]
|
"roles/storage.objectAdmin" = [module.branch-security-sa.iam_email]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ci/cd service account
|
|
||||||
|
|
||||||
module "branch-security-sa-cicd" {
|
|
||||||
source = "../../../modules/iam-service-account"
|
|
||||||
for_each = (
|
|
||||||
lookup(local.cicd_repositories, "security", null) == null
|
|
||||||
? {}
|
|
||||||
: { 0 = local.cicd_repositories.security }
|
|
||||||
)
|
|
||||||
project_id = var.automation.project_id
|
|
||||||
name = "prod-resman-sec-1"
|
|
||||||
description = "Terraform CI/CD stage 2 security service account."
|
|
||||||
prefix = var.prefix
|
|
||||||
iam = {
|
|
||||||
"roles/iam.workloadIdentityUser" = [
|
|
||||||
each.value.branch == null
|
|
||||||
? format(
|
|
||||||
local.identity_providers[each.value.identity_provider].principalset_tpl,
|
|
||||||
var.automation.federated_identity_pool,
|
|
||||||
each.value.name
|
|
||||||
)
|
|
||||||
: format(
|
|
||||||
local.identity_providers[each.value.identity_provider].principal_tpl,
|
|
||||||
var.automation.federated_identity_pool,
|
|
||||||
each.value.name,
|
|
||||||
each.value.branch
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
iam_storage_roles = {
|
|
||||||
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -132,7 +132,7 @@ module "branch-teams-dev-pf-sa" {
|
||||||
prefix = var.prefix
|
prefix = var.prefix
|
||||||
iam = {
|
iam = {
|
||||||
"roles/iam.serviceAccountTokenCreator" = compact([
|
"roles/iam.serviceAccountTokenCreator" = compact([
|
||||||
try(module.branch-pf-dev-sa-cicd.0.iam_email, null)
|
try(module.branch-teams-dev-pf-sa-cicd.0.iam_email, null)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
iam_storage_roles = {
|
iam_storage_roles = {
|
||||||
|
@ -149,7 +149,7 @@ module "branch-teams-prod-pf-sa" {
|
||||||
prefix = var.prefix
|
prefix = var.prefix
|
||||||
iam = {
|
iam = {
|
||||||
"roles/iam.serviceAccountTokenCreator" = compact([
|
"roles/iam.serviceAccountTokenCreator" = compact([
|
||||||
try(module.branch-pf-prod-sa-cicd.0.iam_email, null)
|
try(module.branch-teams-prod-pf-sa-cicd.0.iam_email, null)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
iam_storage_roles = {
|
iam_storage_roles = {
|
||||||
|
@ -180,67 +180,3 @@ module "branch-teams-prod-pf-gcs" {
|
||||||
"roles/storage.objectAdmin" = [module.branch-teams-prod-pf-sa.iam_email]
|
"roles/storage.objectAdmin" = [module.branch-teams-prod-pf-sa.iam_email]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# project factory per-team environment CI/CD service accounts
|
|
||||||
|
|
||||||
module "branch-pf-dev-sa-cicd" {
|
|
||||||
source = "../../../modules/iam-service-account"
|
|
||||||
for_each = (
|
|
||||||
lookup(local.cicd_repositories, "pf_dev", null) == null
|
|
||||||
? {}
|
|
||||||
: { 0 = local.cicd_repositories.pf_dev }
|
|
||||||
)
|
|
||||||
project_id = var.automation.project_id
|
|
||||||
name = "dev-resman-pf-1"
|
|
||||||
description = "Terraform CI/CD project factory development service account."
|
|
||||||
prefix = var.prefix
|
|
||||||
iam = {
|
|
||||||
"roles/iam.workloadIdentityUser" = [
|
|
||||||
each.value.branch == null
|
|
||||||
? format(
|
|
||||||
local.identity_providers[each.value.identity_provider].principalset_tpl,
|
|
||||||
each.value.name
|
|
||||||
)
|
|
||||||
: format(
|
|
||||||
local.identity_providers[each.value.identity_provider].principal_tpl,
|
|
||||||
each.value.name,
|
|
||||||
each.value.branch
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
iam_storage_roles = {
|
|
||||||
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module "branch-pf-prod-sa-cicd" {
|
|
||||||
source = "../../../modules/iam-service-account"
|
|
||||||
for_each = (
|
|
||||||
lookup(local.cicd_repositories, "pf_prod", null) == null
|
|
||||||
? {}
|
|
||||||
: { 0 = local.cicd_repositories.pf_prod }
|
|
||||||
)
|
|
||||||
project_id = var.automation.project_id
|
|
||||||
name = "prod-resman-pf-1"
|
|
||||||
description = "Terraform CI/CD project factory production service account."
|
|
||||||
prefix = var.prefix
|
|
||||||
iam = {
|
|
||||||
"roles/iam.workloadIdentityUser" = [
|
|
||||||
each.value.branch == null
|
|
||||||
? format(
|
|
||||||
local.identity_providers[each.value.identity_provider].principalset_tpl,
|
|
||||||
var.automation.federated_identity_pool,
|
|
||||||
each.value.name
|
|
||||||
)
|
|
||||||
: format(
|
|
||||||
local.identity_providers[each.value.identity_provider].principal_tpl,
|
|
||||||
var.automation.federated_identity_pool,
|
|
||||||
each.value.name,
|
|
||||||
each.value.branch
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
iam_storage_roles = {
|
|
||||||
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2022 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
# tfdoc:file:description CI/CD resources for the data platform branch.
|
||||||
|
|
||||||
|
# source repositories
|
||||||
|
|
||||||
|
module "branch-dp-dev-cicd-repo" {
|
||||||
|
source = "../../../modules/source-repository"
|
||||||
|
for_each = (
|
||||||
|
try(local.cicd_repositories.data_platform_dev.type, null) == "sourcerepo"
|
||||||
|
? { 0 = local.cicd_repositories.data_platform_dev }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
project_id = var.automation.project_id
|
||||||
|
name = each.value.name
|
||||||
|
iam = {
|
||||||
|
"roles/source.admin" = [module.branch-dp-dev-sa.iam_email]
|
||||||
|
"roles/source.reader" = [module.branch-dp-dev-sa-cicd.0.iam_email]
|
||||||
|
}
|
||||||
|
triggers = {
|
||||||
|
fast-03-dp-dev = {
|
||||||
|
filename = ".cloudbuild/workflow.yaml"
|
||||||
|
included_files = [
|
||||||
|
"**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
|
||||||
|
]
|
||||||
|
service_account = module.branch-dp-dev-sa.iam_email
|
||||||
|
substitutions = {}
|
||||||
|
template = {
|
||||||
|
project_id = null
|
||||||
|
branch_name = each.value.branch
|
||||||
|
repo_name = each.value.name
|
||||||
|
tag_name = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module "branch-dp-prod-cicd-repo" {
|
||||||
|
source = "../../../modules/source-repository"
|
||||||
|
for_each = (
|
||||||
|
try(local.cicd_repositories.data_platform_prod.type, null) == "sourcerepo"
|
||||||
|
? { 0 = local.cicd_repositories.data_platform_prod }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
project_id = var.automation.project_id
|
||||||
|
name = each.value.name
|
||||||
|
iam = {
|
||||||
|
"roles/source.admin" = [module.branch-dp-prod-sa.iam_email]
|
||||||
|
"roles/source.reader" = [module.branch-dp-prod-sa-cicd.0.iam_email]
|
||||||
|
}
|
||||||
|
triggers = {
|
||||||
|
fast-03-dp-prod = {
|
||||||
|
filename = ".cloudbuild/workflow.yaml"
|
||||||
|
included_files = [
|
||||||
|
"**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
|
||||||
|
]
|
||||||
|
service_account = module.branch-dp-prod-sa.iam_email
|
||||||
|
substitutions = {}
|
||||||
|
template = {
|
||||||
|
project_id = null
|
||||||
|
branch_name = each.value.branch
|
||||||
|
repo_name = each.value.name
|
||||||
|
tag_name = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# SAs used by CI/CD workflows to impersonate automation SAs
|
||||||
|
|
||||||
|
module "branch-dp-dev-sa-cicd" {
|
||||||
|
source = "../../../modules/iam-service-account"
|
||||||
|
for_each = (
|
||||||
|
try(local.cicd_repositories.data_platform_dev.name, null) != null
|
||||||
|
? { 0 = local.cicd_repositories.data_platform_dev }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
project_id = var.automation.project_id
|
||||||
|
name = "dev-resman-dp-1"
|
||||||
|
description = "Terraform CI/CD data platform development service account."
|
||||||
|
prefix = var.prefix
|
||||||
|
iam = (
|
||||||
|
each.value.type == "sourcerepo"
|
||||||
|
# used directly from the cloud build trigger for source repos
|
||||||
|
? {}
|
||||||
|
# impersonated via workload identity federation for external repos
|
||||||
|
: {
|
||||||
|
"roles/iam.workloadIdentityUser" = [
|
||||||
|
each.value.branch == null
|
||||||
|
? format(
|
||||||
|
local.identity_providers[each.value.identity_provider].principalset_tpl,
|
||||||
|
each.value.name
|
||||||
|
)
|
||||||
|
: format(
|
||||||
|
local.identity_providers[each.value.identity_provider].principal_tpl,
|
||||||
|
each.value.name,
|
||||||
|
each.value.branch
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
iam_project_roles = {
|
||||||
|
(var.automation.project_id) = ["roles/logging.logWriter"]
|
||||||
|
}
|
||||||
|
iam_storage_roles = {
|
||||||
|
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module "branch-dp-prod-sa-cicd" {
|
||||||
|
source = "../../../modules/iam-service-account"
|
||||||
|
for_each = (
|
||||||
|
try(local.cicd_repositories.data_platform_prod.name, null) != null
|
||||||
|
? { 0 = local.cicd_repositories.data_platform_prod }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
project_id = var.automation.project_id
|
||||||
|
name = "prod-resman-dp-1"
|
||||||
|
description = "Terraform CI/CD data platform production service account."
|
||||||
|
prefix = var.prefix
|
||||||
|
iam = (
|
||||||
|
each.value.type == "sourcerepo"
|
||||||
|
# used directly from the cloud build trigger for source repos
|
||||||
|
? {}
|
||||||
|
# impersonated via workload identity federation for external repos
|
||||||
|
: {
|
||||||
|
"roles/iam.workloadIdentityUser" = [
|
||||||
|
each.value.branch == null
|
||||||
|
? format(
|
||||||
|
local.identity_providers[each.value.identity_provider].principalset_tpl,
|
||||||
|
var.automation.federated_identity_pool,
|
||||||
|
each.value.name
|
||||||
|
)
|
||||||
|
: format(
|
||||||
|
local.identity_providers[each.value.identity_provider].principal_tpl,
|
||||||
|
var.automation.federated_identity_pool,
|
||||||
|
each.value.name,
|
||||||
|
each.value.branch
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
iam_project_roles = {
|
||||||
|
(var.automation.project_id) = ["roles/logging.logWriter"]
|
||||||
|
}
|
||||||
|
iam_storage_roles = {
|
||||||
|
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2022 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
# tfdoc:file:description CI/CD resources for the networking branch.
|
||||||
|
|
||||||
|
# source repository
|
||||||
|
|
||||||
|
module "branch-network-cicd-repo" {
|
||||||
|
source = "../../../modules/source-repository"
|
||||||
|
for_each = (
|
||||||
|
try(local.cicd_repositories.networking.type, null) == "sourcerepo"
|
||||||
|
? { 0 = local.cicd_repositories.networking }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
project_id = var.automation.project_id
|
||||||
|
name = each.value.name
|
||||||
|
iam = {
|
||||||
|
"roles/source.admin" = [module.branch-network-sa.iam_email]
|
||||||
|
"roles/source.reader" = [module.branch-network-sa-cicd.0.iam_email]
|
||||||
|
}
|
||||||
|
triggers = {
|
||||||
|
fast-02-networking = {
|
||||||
|
filename = ".cloudbuild/workflow.yaml"
|
||||||
|
included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
|
||||||
|
service_account = module.branch-network-sa.id
|
||||||
|
substitutions = {}
|
||||||
|
template = {
|
||||||
|
project_id = null
|
||||||
|
branch_name = each.value.branch
|
||||||
|
repo_name = each.value.name
|
||||||
|
tag_name = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# SA used by CI/CD workflows to impersonate automation SAs
|
||||||
|
|
||||||
|
module "branch-network-sa-cicd" {
|
||||||
|
source = "../../../modules/iam-service-account"
|
||||||
|
for_each = (
|
||||||
|
try(local.cicd_repositories.networking.name, null) != null
|
||||||
|
? { 0 = local.cicd_repositories.networking }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
project_id = var.automation.project_id
|
||||||
|
name = "prod-resman-net-1"
|
||||||
|
description = "Terraform CI/CD stage 2 networking service account."
|
||||||
|
prefix = var.prefix
|
||||||
|
iam = (
|
||||||
|
each.value.type == "sourcerepo"
|
||||||
|
# used directly from the cloud build trigger for source repos
|
||||||
|
? {}
|
||||||
|
# impersonated via workload identity federation for external repos
|
||||||
|
: {
|
||||||
|
"roles/iam.workloadIdentityUser" = [
|
||||||
|
each.value.branch == null
|
||||||
|
? format(
|
||||||
|
local.identity_providers[each.value.identity_provider].principalset_tpl,
|
||||||
|
var.automation.federated_identity_pool,
|
||||||
|
each.value.name
|
||||||
|
)
|
||||||
|
: format(
|
||||||
|
local.identity_providers[each.value.identity_provider].principal_tpl,
|
||||||
|
var.automation.federated_identity_pool,
|
||||||
|
each.value.name,
|
||||||
|
each.value.branch
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
iam_project_roles = {
|
||||||
|
(var.automation.project_id) = ["roles/logging.logWriter"]
|
||||||
|
}
|
||||||
|
iam_storage_roles = {
|
||||||
|
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2022 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
# tfdoc:file:description CI/CD resources for the security branch.
|
||||||
|
|
||||||
|
# source repository
|
||||||
|
|
||||||
|
module "branch-security-cicd-repo" {
|
||||||
|
source = "../../../modules/source-repository"
|
||||||
|
for_each = (
|
||||||
|
try(local.cicd_repositories.security.type, null) == "sourcerepo"
|
||||||
|
? { 0 = local.cicd_repositories.security }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
project_id = var.automation.project_id
|
||||||
|
name = each.value.name
|
||||||
|
iam = {
|
||||||
|
"roles/source.admin" = [module.branch-security-sa.iam_email]
|
||||||
|
"roles/source.reader" = [module.branch-security-sa-cicd.0.iam_email]
|
||||||
|
}
|
||||||
|
triggers = {
|
||||||
|
fast-02-security = {
|
||||||
|
filename = ".cloudbuild/workflow.yaml"
|
||||||
|
included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
|
||||||
|
service_account = module.branch-security-sa.id
|
||||||
|
substitutions = {}
|
||||||
|
template = {
|
||||||
|
project_id = null
|
||||||
|
branch_name = each.value.branch
|
||||||
|
repo_name = each.value.name
|
||||||
|
tag_name = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# SA used by CI/CD workflows to impersonate automation SAs
|
||||||
|
|
||||||
|
module "branch-security-sa-cicd" {
|
||||||
|
source = "../../../modules/iam-service-account"
|
||||||
|
for_each = (
|
||||||
|
try(local.cicd_repositories.security.name, null) != null
|
||||||
|
? { 0 = local.cicd_repositories.security }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
project_id = var.automation.project_id
|
||||||
|
name = "prod-resman-sec-1"
|
||||||
|
description = "Terraform CI/CD stage 2 security service account."
|
||||||
|
prefix = var.prefix
|
||||||
|
iam = (
|
||||||
|
each.value.type == "sourcerepo"
|
||||||
|
# used directly from the cloud build trigger for source repos
|
||||||
|
? {}
|
||||||
|
# impersonated via workload identity federation for external repos
|
||||||
|
: {
|
||||||
|
"roles/iam.workloadIdentityUser" = [
|
||||||
|
each.value.branch == null
|
||||||
|
? format(
|
||||||
|
local.identity_providers[each.value.identity_provider].principalset_tpl,
|
||||||
|
var.automation.federated_identity_pool,
|
||||||
|
each.value.name
|
||||||
|
)
|
||||||
|
: format(
|
||||||
|
local.identity_providers[each.value.identity_provider].principal_tpl,
|
||||||
|
var.automation.federated_identity_pool,
|
||||||
|
each.value.name,
|
||||||
|
each.value.branch
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
iam_project_roles = {
|
||||||
|
(var.automation.project_id) = ["roles/logging.logWriter"]
|
||||||
|
}
|
||||||
|
iam_storage_roles = {
|
||||||
|
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2022 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
# tfdoc:file:description CI/CD resources for the teams branch.
|
||||||
|
|
||||||
|
# source repositories
|
||||||
|
|
||||||
|
module "branch-teams-dev-pf-cicd-repo" {
|
||||||
|
source = "../../../modules/source-repository"
|
||||||
|
for_each = (
|
||||||
|
try(local.cicd_repositories.project_factory_dev.type, null) == "sourcerepo"
|
||||||
|
? { 0 = local.cicd_repositories.project_factory_dev }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
project_id = var.automation.project_id
|
||||||
|
name = each.value.name
|
||||||
|
iam = {
|
||||||
|
"roles/source.admin" = [module.branch-teams-dev-pf-sa.iam_email]
|
||||||
|
"roles/source.reader" = [module.branch-teams-dev-pf-sa-cicd.0.iam_email]
|
||||||
|
}
|
||||||
|
triggers = {
|
||||||
|
fast-03-pf-dev = {
|
||||||
|
filename = ".cloudbuild/workflow.yaml"
|
||||||
|
included_files = [
|
||||||
|
"**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
|
||||||
|
]
|
||||||
|
service_account = module.branch-teams-dev-pf-sa.iam_email
|
||||||
|
substitutions = {}
|
||||||
|
template = {
|
||||||
|
project_id = null
|
||||||
|
branch_name = each.value.branch
|
||||||
|
repo_name = each.value.name
|
||||||
|
tag_name = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module "branch-teams-prod-pf-cicd-repo" {
|
||||||
|
source = "../../../modules/source-repository"
|
||||||
|
for_each = (
|
||||||
|
try(local.cicd_repositories.project_factory_prod.type, null) == "sourcerepo"
|
||||||
|
? { 0 = local.cicd_repositories.project_factory_prod }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
project_id = var.automation.project_id
|
||||||
|
name = each.value.name
|
||||||
|
iam = {
|
||||||
|
"roles/source.admin" = [module.branch-teams-prod-pf-sa.iam_email]
|
||||||
|
"roles/source.reader" = [module.branch-teams-prod-pf-sa-cicd.0.iam_email]
|
||||||
|
}
|
||||||
|
triggers = {
|
||||||
|
fast-03-pf-prod = {
|
||||||
|
filename = ".cloudbuild/workflow.yaml"
|
||||||
|
included_files = [
|
||||||
|
"**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
|
||||||
|
]
|
||||||
|
service_account = module.branch-teams-prod-pf-sa.iam_email
|
||||||
|
substitutions = {}
|
||||||
|
template = {
|
||||||
|
project_id = null
|
||||||
|
branch_name = each.value.branch
|
||||||
|
repo_name = each.value.name
|
||||||
|
tag_name = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# SAs used by CI/CD workflows to impersonate automation SAs
|
||||||
|
|
||||||
|
module "branch-teams-dev-pf-sa-cicd" {
|
||||||
|
source = "../../../modules/iam-service-account"
|
||||||
|
for_each = (
|
||||||
|
try(local.cicd_repositories.project_factory_dev.name, null) != null
|
||||||
|
? { 0 = local.cicd_repositories.project_factory_dev }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
project_id = var.automation.project_id
|
||||||
|
name = "dev-pf-resman-pf-1"
|
||||||
|
description = "Terraform CI/CD project factory development service account."
|
||||||
|
prefix = var.prefix
|
||||||
|
iam = (
|
||||||
|
each.value.type == "sourcerepo"
|
||||||
|
# used directly from the cloud build trigger for source repos
|
||||||
|
? {}
|
||||||
|
# impersonated via workload identity federation for external repos
|
||||||
|
: {
|
||||||
|
"roles/iam.workloadIdentityUser" = [
|
||||||
|
each.value.branch == null
|
||||||
|
? format(
|
||||||
|
local.identity_providers[each.value.identity_provider].principalset_tpl,
|
||||||
|
each.value.name
|
||||||
|
)
|
||||||
|
: format(
|
||||||
|
local.identity_providers[each.value.identity_provider].principal_tpl,
|
||||||
|
each.value.name,
|
||||||
|
each.value.branch
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
iam_project_roles = {
|
||||||
|
(var.automation.project_id) = ["roles/logging.logWriter"]
|
||||||
|
}
|
||||||
|
iam_storage_roles = {
|
||||||
|
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module "branch-teams-prod-pf-sa-cicd" {
|
||||||
|
source = "../../../modules/iam-service-account"
|
||||||
|
for_each = (
|
||||||
|
try(local.cicd_repositories.project_factory_prod.name, null) != null
|
||||||
|
? { 0 = local.cicd_repositories.project_factory_prod }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
project_id = var.automation.project_id
|
||||||
|
name = "prod-pf-resman-pf-1"
|
||||||
|
description = "Terraform CI/CD project factory production service account."
|
||||||
|
prefix = var.prefix
|
||||||
|
iam = (
|
||||||
|
each.value.type == "sourcerepo"
|
||||||
|
# used directly from the cloud build trigger for source repos
|
||||||
|
? {}
|
||||||
|
# impersonated via workload identity federation for external repos
|
||||||
|
: {
|
||||||
|
"roles/iam.workloadIdentityUser" = [
|
||||||
|
each.value.branch == null
|
||||||
|
? format(
|
||||||
|
local.identity_providers[each.value.identity_provider].principalset_tpl,
|
||||||
|
var.automation.federated_identity_pool,
|
||||||
|
each.value.name
|
||||||
|
)
|
||||||
|
: format(
|
||||||
|
local.identity_providers[each.value.identity_provider].principal_tpl,
|
||||||
|
var.automation.federated_identity_pool,
|
||||||
|
each.value.name,
|
||||||
|
each.value.branch
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
iam_project_roles = {
|
||||||
|
(var.automation.project_id) = ["roles/logging.logWriter"]
|
||||||
|
}
|
||||||
|
iam_storage_roles = {
|
||||||
|
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,11 +24,33 @@ locals {
|
||||||
if(
|
if(
|
||||||
v != null
|
v != null
|
||||||
&&
|
&&
|
||||||
contains(keys(local.identity_providers), try(v.identity_provider, ""))
|
(
|
||||||
|
try(v.type, null) == "sourcerepo"
|
||||||
|
||
|
||||||
|
contains(
|
||||||
|
keys(local.identity_providers),
|
||||||
|
coalesce(try(v.identity_provider, null), ":")
|
||||||
|
)
|
||||||
|
)
|
||||||
&&
|
&&
|
||||||
fileexists("${path.module}/templates/workflow-${try(v.type, "")}.yaml")
|
fileexists("${path.module}/templates/workflow-${try(v.type, "")}.yaml")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
cicd_workflow_var_files = {
|
||||||
|
stage_2 = [
|
||||||
|
"00-bootstrap.auto.tfvars.json",
|
||||||
|
"01-resman.auto.tfvars.json",
|
||||||
|
"globals.auto.tfvars.json"
|
||||||
|
]
|
||||||
|
stage_3 = [
|
||||||
|
"00-bootstrap.auto.tfvars.json",
|
||||||
|
"01-resman.auto.tfvars.json",
|
||||||
|
"globals.auto.tfvars.json",
|
||||||
|
"02-networking.auto.tfvars.json",
|
||||||
|
"02-security.auto.tfvars.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
custom_roles = coalesce(var.custom_roles, {})
|
custom_roles = coalesce(var.custom_roles, {})
|
||||||
groups = {
|
groups = {
|
||||||
for k, v in var.groups :
|
for k, v in var.groups :
|
||||||
|
|
|
@ -15,51 +15,37 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
_cicd_tf_var_files = {
|
|
||||||
stage_2 = [
|
|
||||||
"00-bootstrap.auto.tfvars.json",
|
|
||||||
"01-resman.auto.tfvars.json",
|
|
||||||
"globals.auto.tfvars.json"
|
|
||||||
]
|
|
||||||
stage_3 = [
|
|
||||||
"00-bootstrap.auto.tfvars.json",
|
|
||||||
"01-resman.auto.tfvars.json",
|
|
||||||
"globals.auto.tfvars.json",
|
|
||||||
"02-networking.auto.tfvars.json",
|
|
||||||
"02-security.auto.tfvars.json"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
_tpl_providers = "${path.module}/templates/providers.tf.tpl"
|
_tpl_providers = "${path.module}/templates/providers.tf.tpl"
|
||||||
cicd_workflow_attrs = {
|
cicd_workflow_attrs = {
|
||||||
data_platform_dev = {
|
data_platform_dev = {
|
||||||
service_account = try(module.branch-dp-dev-sa-cicd.0.email, null)
|
service_account = try(module.branch-dp-dev-sa-cicd.0.email, null)
|
||||||
tf_providers_file = "03-data-platform-dev-providers.tf"
|
tf_providers_file = "03-data-platform-dev-providers.tf"
|
||||||
tf_var_files = local._cicd_tf_var_files.stage_3
|
tf_var_files = local.cicd_workflow_var_files.stage_3
|
||||||
}
|
}
|
||||||
data_platform_prod = {
|
data_platform_prod = {
|
||||||
service_account = try(module.branch-dp-prod-sa-cicd.0.email, null)
|
service_account = try(module.branch-dp-prod-sa-cicd.0.email, null)
|
||||||
tf_providers_file = "03-data-platform-prod-providers.tf"
|
tf_providers_file = "03-data-platform-prod-providers.tf"
|
||||||
tf_var_files = local._cicd_tf_var_files.stage_3
|
tf_var_files = local.cicd_workflow_var_files.stage_3
|
||||||
}
|
}
|
||||||
networking = {
|
networking = {
|
||||||
service_account = try(module.branch-network-sa-cicd.0.email, null)
|
service_account = try(module.branch-network-sa-cicd.0.email, null)
|
||||||
tf_providers_file = "02-networking-providers.tf"
|
tf_providers_file = "02-networking-providers.tf"
|
||||||
tf_var_files = local._cicd_tf_var_files.stage_2
|
tf_var_files = local.cicd_workflow_var_files.stage_2
|
||||||
}
|
}
|
||||||
project_factory_dev = {
|
project_factory_dev = {
|
||||||
service_account = try(module.branch-pf-dev-sa-cicd.0.email, null)
|
service_account = try(module.branch-teams-dev-pf-sa-cicd.0.email, null)
|
||||||
tf_providers_file = "03-project-factory-dev-providers.tf"
|
tf_providers_file = "03-project-factory-dev-providers.tf"
|
||||||
tf_var_files = local._cicd_tf_var_files.stage_3
|
tf_var_files = local.cicd_workflow_var_files.stage_3
|
||||||
}
|
}
|
||||||
project_factory_prod = {
|
project_factory_prod = {
|
||||||
service_account = try(module.branch-pf-prod-sa-cicd.0.email, null)
|
service_account = try(module.branch-teams-prod-pf-sa-cicd.0.email, null)
|
||||||
tf_providers_file = "03-project-factory-prod-providers.tf"
|
tf_providers_file = "03-project-factory-prod-providers.tf"
|
||||||
tf_var_files = local._cicd_tf_var_files.stage_3
|
tf_var_files = local.cicd_workflow_var_files.stage_3
|
||||||
}
|
}
|
||||||
security = {
|
security = {
|
||||||
service_account = try(module.branch-security-sa-cicd.0.email, null)
|
service_account = try(module.branch-security-sa-cicd.0.email, null)
|
||||||
tf_providers_file = "02-security-providers.tf"
|
tf_providers_file = "02-security-providers.tf"
|
||||||
tf_var_files = local._cicd_tf_var_files.stage_2
|
tf_var_files = local.cicd_workflow_var_files.stage_2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cicd_workflows = {
|
cicd_workflows = {
|
||||||
|
|
|
@ -87,22 +87,29 @@ variable "cicd_repositories" {
|
||||||
validation {
|
validation {
|
||||||
condition = alltrue([
|
condition = alltrue([
|
||||||
for k, v in coalesce(var.cicd_repositories, {}) :
|
for k, v in coalesce(var.cicd_repositories, {}) :
|
||||||
v == null || (
|
v == null || try(v.name, null) != null
|
||||||
try(v.name, null) != null
|
|
||||||
&&
|
|
||||||
try(v.identity_provider, null) != null
|
|
||||||
)
|
|
||||||
])
|
])
|
||||||
error_message = "Non-null repositories need non-null name and providers."
|
error_message = "Non-null repositories need a non-null name."
|
||||||
}
|
}
|
||||||
validation {
|
validation {
|
||||||
condition = alltrue([
|
condition = alltrue([
|
||||||
for k, v in coalesce(var.cicd_repositories, {}) :
|
for k, v in coalesce(var.cicd_repositories, {}) :
|
||||||
v == null || (
|
v == null || (
|
||||||
contains(["gitlab", "github"], coalesce(try(v.type, null), "null"))
|
try(v.identity_provider, null) != null
|
||||||
|
||
|
||||||
|
try(v.type, null) == "sourcerepo"
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
error_message = "Invalid repository type, supported types: 'github' or 'gitlab'."
|
error_message = "Non-null repositories need a non-null provider unless type is 'sourcerepo'."
|
||||||
|
}
|
||||||
|
validation {
|
||||||
|
condition = alltrue([
|
||||||
|
for k, v in coalesce(var.cicd_repositories, {}) :
|
||||||
|
v == null || (
|
||||||
|
contains(["github", "gitlab", "sourcerepo"], coalesce(try(v.type, null), "null"))
|
||||||
|
)
|
||||||
|
])
|
||||||
|
error_message = "Invalid repository type, supported types: 'github' 'gitlab' or 'sourcerepo'."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,9 +63,10 @@ module "myproject-default-service-accounts" {
|
||||||
|---|---|:---:|
|
|---|---|:---:|
|
||||||
| [email](outputs.tf#L17) | Service account email. | |
|
| [email](outputs.tf#L17) | Service account email. | |
|
||||||
| [iam_email](outputs.tf#L25) | IAM-format service account email. | |
|
| [iam_email](outputs.tf#L25) | IAM-format service account email. | |
|
||||||
| [key](outputs.tf#L33) | Service account key. | ✓ |
|
| [id](outputs.tf#L33) | Service account id. | |
|
||||||
| [name](outputs.tf#L39) | Service account id. | |
|
| [key](outputs.tf#L38) | Service account key. | ✓ |
|
||||||
| [service_account](outputs.tf#L44) | Service account resource. | |
|
| [name](outputs.tf#L44) | Service account name. | |
|
||||||
| [service_account_credentials](outputs.tf#L49) | Service account json credential templates for uploaded public keys data. | |
|
| [service_account](outputs.tf#L49) | Service account resource. | |
|
||||||
|
| [service_account_credentials](outputs.tf#L54) | Service account json credential templates for uploaded public keys data. | |
|
||||||
|
|
||||||
<!-- END TFDOC -->
|
<!-- END TFDOC -->
|
||||||
|
|
|
@ -30,6 +30,11 @@ output "iam_email" {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output "id" {
|
||||||
|
description = "Service account id."
|
||||||
|
value = local.service_account.id
|
||||||
|
}
|
||||||
|
|
||||||
output "key" {
|
output "key" {
|
||||||
description = "Service account key."
|
description = "Service account key."
|
||||||
sensitive = true
|
sensitive = true
|
||||||
|
@ -37,7 +42,7 @@ output "key" {
|
||||||
}
|
}
|
||||||
|
|
||||||
output "name" {
|
output "name" {
|
||||||
description = "Service account id."
|
description = "Service account name."
|
||||||
value = local.service_account.name
|
value = local.service_account.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
# Google Cloud Source Repository Module
|
# Google Cloud Source Repository Module
|
||||||
|
|
||||||
This module allows managing a single Cloud Source Repository, including IAM bindings.
|
This module allows managing a single Cloud Source Repository, including IAM bindings and basic Cloud Build triggers.
|
||||||
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Simple repository with IAM
|
### Repository with IAM
|
||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
module "repo" {
|
module "repo" {
|
||||||
|
@ -18,21 +17,64 @@ module "repo" {
|
||||||
}
|
}
|
||||||
# tftest modules=1 resources=2
|
# tftest modules=1 resources=2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Repository with Cloud Build trigger
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
module "repo" {
|
||||||
|
source = "./modules/source-repository"
|
||||||
|
project_id = "my-project"
|
||||||
|
name = "my-repo"
|
||||||
|
triggers = {
|
||||||
|
foo = {
|
||||||
|
filename = "ci/workflow-foo.yaml"
|
||||||
|
included_files = ["**/*tf"]
|
||||||
|
service_account = null
|
||||||
|
substitutions = {
|
||||||
|
BAR = 1
|
||||||
|
}
|
||||||
|
template = {
|
||||||
|
branch_name = "main"
|
||||||
|
project_id = null
|
||||||
|
tag_name = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# tftest modules=1 resources=2
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- TFDOC OPTS files:1 -->
|
||||||
<!-- BEGIN TFDOC -->
|
<!-- BEGIN TFDOC -->
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| name | description | resources |
|
||||||
|
|---|---|---|
|
||||||
|
| [iam.tf](./iam.tf) | IAM resources. | <code>google_sourcerepo_repository_iam_binding</code> · <code>google_sourcerepo_repository_iam_member</code> |
|
||||||
|
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_cloudbuild_trigger</code> · <code>google_sourcerepo_repository</code> |
|
||||||
|
| [outputs.tf](./outputs.tf) | Module outputs. | |
|
||||||
|
| [variables.tf](./variables.tf) | Module variables. | |
|
||||||
|
| [versions.tf](./versions.tf) | Version pins. | |
|
||||||
|
|
||||||
## Variables
|
## Variables
|
||||||
|
|
||||||
| name | description | type | required | default |
|
| name | description | type | required | default |
|
||||||
|---|---|:---:|:---:|:---:|
|
|---|---|:---:|:---:|:---:|
|
||||||
| [name](variables.tf#L23) | Repository name. | <code>string</code> | ✓ | |
|
| [name](variables.tf#L44) | Repository name. | <code>string</code> | ✓ | |
|
||||||
| [project_id](variables.tf#L28) | Project used for resources. | <code>string</code> | ✓ | |
|
| [project_id](variables.tf#L49) | Project used for resources. | <code>string</code> | ✓ | |
|
||||||
| [iam](variables.tf#L17) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
| [group_iam](variables.tf#L17) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||||
|
| [iam](variables.tf#L24) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||||
|
| [iam_additive](variables.tf#L31) | IAM additive bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||||
|
| [iam_additive_members](variables.tf#L38) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||||
|
| [triggers](variables.tf#L54) | Cloud Build triggers. | <code title="map(object({ filename = string included_files = list(string) service_account = string substitutions = map(string) template = object({ branch_name = string project_id = string tag_name = string }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
|
||||||
| name | description | sensitive |
|
| name | description | sensitive |
|
||||||
|---|---|:---:|
|
|---|---|:---:|
|
||||||
| [id](outputs.tf#L17) | Repository id. | |
|
| [id](outputs.tf#L17) | Repository id. | |
|
||||||
| [url](outputs.tf#L22) | Repository URL. | |
|
| [name](outputs.tf#L22) | Repository name. | |
|
||||||
|
| [url](outputs.tf#L27) | Repository URL. | |
|
||||||
|
|
||||||
<!-- END TFDOC -->
|
<!-- END TFDOC -->
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2022 Google LLC
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
# tfdoc:file:description IAM resources.
|
||||||
|
|
||||||
|
locals {
|
||||||
|
_group_iam_roles = distinct(flatten(values(var.group_iam)))
|
||||||
|
_group_iam = {
|
||||||
|
for r in local._group_iam_roles : r => [
|
||||||
|
for k, v in var.group_iam : "group:${k}" if try(index(v, r), null) != null
|
||||||
|
]
|
||||||
|
}
|
||||||
|
_iam_additive_pairs = flatten([
|
||||||
|
for role, members in var.iam_additive : [
|
||||||
|
for member in members : { role = role, member = member }
|
||||||
|
]
|
||||||
|
])
|
||||||
|
_iam_additive_member_pairs = flatten([
|
||||||
|
for member, roles in var.iam_additive_members : [
|
||||||
|
for role in roles : { role = role, member = member }
|
||||||
|
]
|
||||||
|
])
|
||||||
|
iam = {
|
||||||
|
for role in distinct(concat(keys(var.iam), keys(local._group_iam))) :
|
||||||
|
role => concat(
|
||||||
|
try(var.iam[role], []),
|
||||||
|
try(local._group_iam[role], [])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
iam_additive = {
|
||||||
|
for pair in concat(local._iam_additive_pairs, local._iam_additive_member_pairs) :
|
||||||
|
"${pair.role}-${pair.member}" => pair
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_sourcerepo_repository_iam_binding" "authoritative" {
|
||||||
|
for_each = local.iam
|
||||||
|
project = var.project_id
|
||||||
|
repository = google_sourcerepo_repository.default.name
|
||||||
|
role = each.key
|
||||||
|
members = each.value
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_sourcerepo_repository_iam_member" "additive" {
|
||||||
|
for_each = (
|
||||||
|
length(var.iam_additive) + length(var.iam_additive_members) > 0
|
||||||
|
? local.iam_additive
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
project = var.project_id
|
||||||
|
repository = google_sourcerepo_repository.default.name
|
||||||
|
role = each.value.role
|
||||||
|
member = each.value.member
|
||||||
|
}
|
|
@ -19,14 +19,18 @@ resource "google_sourcerepo_repository" "default" {
|
||||||
name = var.name
|
name = var.name
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_sourcerepo_repository_iam_binding" "default" {
|
resource "google_cloudbuild_trigger" "default" {
|
||||||
for_each = var.iam
|
for_each = coalesce(var.triggers, {})
|
||||||
project = var.project_id
|
project = var.project_id
|
||||||
repository = google_sourcerepo_repository.default.name
|
name = each.key
|
||||||
role = each.key
|
filename = each.value.filename
|
||||||
members = each.value
|
included_files = each.value.included_files
|
||||||
|
service_account = each.value.service_account
|
||||||
depends_on = [
|
substitutions = each.value.substitutions
|
||||||
google_sourcerepo_repository.default
|
trigger_template {
|
||||||
]
|
project_id = try(each.value.template.project_id, var.project_id)
|
||||||
|
branch_name = try(each.value.template.branch_name, null)
|
||||||
|
repo_name = google_sourcerepo_repository.default.name
|
||||||
|
tag_name = try(each.value.template.tag_name, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,11 @@ output "id" {
|
||||||
value = google_sourcerepo_repository.default.id
|
value = google_sourcerepo_repository.default.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output "name" {
|
||||||
|
description = "Repository name."
|
||||||
|
value = google_sourcerepo_repository.default.name
|
||||||
|
}
|
||||||
|
|
||||||
output "url" {
|
output "url" {
|
||||||
description = "Repository URL."
|
description = "Repository URL."
|
||||||
value = google_sourcerepo_repository.default.url
|
value = google_sourcerepo_repository.default.url
|
||||||
|
|
|
@ -14,10 +14,31 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
variable "group_iam" {
|
||||||
|
description = "Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable."
|
||||||
|
type = map(list(string))
|
||||||
|
default = {}
|
||||||
|
nullable = false
|
||||||
|
}
|
||||||
|
|
||||||
variable "iam" {
|
variable "iam" {
|
||||||
description = "IAM bindings in {ROLE => [MEMBERS]} format."
|
description = "IAM bindings in {ROLE => [MEMBERS]} format."
|
||||||
type = map(list(string))
|
type = map(list(string))
|
||||||
default = {}
|
default = {}
|
||||||
|
nullable = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "iam_additive" {
|
||||||
|
description = "IAM additive bindings in {ROLE => [MEMBERS]} format."
|
||||||
|
type = map(list(string))
|
||||||
|
default = {}
|
||||||
|
nullable = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "iam_additive_members" {
|
||||||
|
description = "IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values."
|
||||||
|
type = map(list(string))
|
||||||
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "name" {
|
variable "name" {
|
||||||
|
@ -29,3 +50,20 @@ variable "project_id" {
|
||||||
description = "Project used for resources."
|
description = "Project used for resources."
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "triggers" {
|
||||||
|
description = "Cloud Build triggers."
|
||||||
|
type = map(object({
|
||||||
|
filename = string
|
||||||
|
included_files = list(string)
|
||||||
|
service_account = string
|
||||||
|
substitutions = map(string)
|
||||||
|
template = object({
|
||||||
|
branch_name = string
|
||||||
|
project_id = string
|
||||||
|
tag_name = string
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
nullable = false
|
||||||
|
}
|
||||||
|
|
|
@ -14,9 +14,52 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
variable "group_iam" {
|
||||||
|
type = any
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "iam" {
|
||||||
|
type = any
|
||||||
|
default = {}
|
||||||
|
nullable = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "iam_additive" {
|
||||||
|
type = any
|
||||||
|
default = {}
|
||||||
|
nullable = false
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "iam_additive_members" {
|
||||||
|
type = any
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "name" {
|
||||||
|
description = "Repository name."
|
||||||
|
type = string
|
||||||
|
default = "test"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "project_id" {
|
||||||
|
description = "Project used for resources."
|
||||||
|
type = string
|
||||||
|
default = "test"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "triggers" {
|
||||||
|
type = any
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
module "test" {
|
module "test" {
|
||||||
source = "../../../../modules/source-repository"
|
source = "../../../../modules/source-repository"
|
||||||
project_id = var.project_id
|
project_id = var.project_id
|
||||||
name = var.name
|
name = var.name
|
||||||
|
group_iam = var.group_iam
|
||||||
iam = var.iam
|
iam = var.iam
|
||||||
|
iam_additive = var.iam_additive
|
||||||
|
iam_additive_members = var.iam_additive_members
|
||||||
|
triggers = var.triggers
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2022 Google LLC
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
variable "project_id" {
|
|
||||||
type = string
|
|
||||||
default = "test-project"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "iam" {
|
|
||||||
type = map(list(string))
|
|
||||||
default = {
|
|
||||||
"roles/source.reader" = ["foo@example.org"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "name" {
|
|
||||||
type = string
|
|
||||||
default = "test"
|
|
||||||
}
|
|
|
@ -12,23 +12,49 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
|
def test_resource_count(plan_runner):
|
||||||
@pytest.fixture
|
'Test number of resources created.'
|
||||||
def resources(plan_runner):
|
|
||||||
_, resources = plan_runner()
|
_, resources = plan_runner()
|
||||||
return resources
|
assert len(resources) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_resource_count(resources):
|
def test_iam(plan_runner):
|
||||||
"Test number of resources created."
|
'Test IAM binding resources.'
|
||||||
assert len(resources) == 2
|
group_iam = '{"fooers@example.org"=["roles/owner"]}'
|
||||||
|
iam = '''{
|
||||||
|
"roles/editor" = ["user:a@example.org", "user:b@example.org"]
|
||||||
|
"roles/owner" = ["user:c@example.org"]
|
||||||
|
}'''
|
||||||
|
_, resources = plan_runner(group_iam=group_iam, iam=iam)
|
||||||
|
bindings = {
|
||||||
|
r['values']['role']: r['values']['members']
|
||||||
|
for r in resources
|
||||||
|
if r['type'] == 'google_sourcerepo_repository_iam_binding'
|
||||||
|
}
|
||||||
|
assert bindings == {
|
||||||
|
'roles/editor': ['user:a@example.org', 'user:b@example.org'],
|
||||||
|
'roles/owner': ['group:fooers@example.org', 'user:c@example.org']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_iam(resources):
|
def test_triggers(plan_runner):
|
||||||
"Test IAM binding resources."
|
'Test trigger resources.'
|
||||||
bindings = [r['values'] for r in resources if r['type']
|
triggers = '''{
|
||||||
== 'google_sourcerepo_repository_iam_binding']
|
foo = {
|
||||||
assert len(bindings) == 1
|
filename = "ci/foo.yaml"
|
||||||
assert bindings[0]['role'] == 'roles/source.reader'
|
included_files = ["**/*yaml"]
|
||||||
|
service_account = null
|
||||||
|
substitutions = null
|
||||||
|
template = {
|
||||||
|
branch_name = null
|
||||||
|
project_id = null
|
||||||
|
tag_name = "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}'''
|
||||||
|
_, resources = plan_runner(triggers=triggers)
|
||||||
|
triggers = [
|
||||||
|
r['index'] for r in resources if r['type'] == 'google_cloudbuild_trigger'
|
||||||
|
]
|
||||||
|
assert triggers == ['foo']
|
Loading…
Reference in New Issue