diff --git a/.gitignore b/.gitignore index 79fa83df50..91778178cf 100644 --- a/.gitignore +++ b/.gitignore @@ -21,13 +21,13 @@ bundle.zip **/*.pkrvars.hcl fixture_* fast/configs -fast/stages/**/[0-9]*providers.tf -fast/stages/**/terraform.tfvars -fast/stages/**/terraform.tfvars.json -fast/stages/**/terraform-*.auto.tfvars.json -fast/stages/**/0*.auto.tfvars* +fast/**/[0-9]*providers.tf +fast/**/terraform.tfvars +fast/**/terraform.tfvars.json +fast/**/terraform-*.auto.tfvars.json +fast/**/[0-9]*.auto.tfvars* **/node_modules -fast/stages/**/globals.auto.tfvars.json +fast/**/globals.auto.tfvars.json cloud_sql_proxy examples/cloud-operations/binauthz/tenant-setup.yaml examples/cloud-operations/binauthz/app/app.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index cedd3a40db..c365749440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -480,7 +480,7 @@ All notable changes to this project will be documented in this file. - fix `tag` output on `data-catalog-policy-tag` module - add shared-vpc support on `gcs-to-bq-with-least-privileges` - new `net-ilb-l7` module -- new [02-networking-peering](fast/stages/02-networking-peering) networking stage +- new `02-networking-peering` networking stage - **incompatible change** the variable for PSA ranges in networking stages have changed ## [14.0.0] - 2022-02-25 @@ -499,8 +499,8 @@ All notable changes to this project will be documented in this file. - **incompatible change** removed `ingress_settings` configuration option in the `cloud-functions` module. - new [m4ce VM example](blueprints/cloud-operations/vm-migration/) - Support for resource management tags in the `organization`, `folder`, `project`, `compute-vm`, and `kms` modules -- new [data platform](fast/stages/03-data-platform) stage 3 -- new [02-networking-nva](fast/stages/02-networking-nva) networking stage +- new `data platform` stage 3 +- new `02-networking-nva` networking stage - allow customizing the names of custom roles - added `environment` and `context` resource management tags - use resource management tags to restrict scope of roles/orgpolicy.policyAdmin diff --git a/blueprints/data-solutions/data-platform-foundations/README.md b/blueprints/data-solutions/data-platform-foundations/README.md index b038cfe4b6..a05bbae774 100644 --- a/blueprints/data-solutions/data-platform-foundations/README.md +++ b/blueprints/data-solutions/data-platform-foundations/README.md @@ -21,7 +21,7 @@ The approach adapts to different high-level requirements: - least privilege principle - rely on service account impersonation -The code in this blueprint doesn't address Organization-level configurations (Organization policy, VPC-SC, centralized logs). We expect those elements to be managed by automation stages external to this script like those in [FAST](../../../fast) and this blueprint deployed on top of them as one of the [stages](../../../fast/stages/03-data-platform/dev/README.md). +The code in this blueprint doesn't address Organization-level configurations (Organization policy, VPC-SC, centralized logs). We expect those elements to be managed by automation stages external to this script like those in [FAST](../../../fast) and this blueprint deployed on top of them as one of the [stages](../../../fast/stages/3-data-platform/dev/README.md). ### Project structure diff --git a/blueprints/gke/multitenant-fleet/README.md b/blueprints/gke/multitenant-fleet/README.md index 1e09afaa29..cadcf4109b 100644 --- a/blueprints/gke/multitenant-fleet/README.md +++ b/blueprints/gke/multitenant-fleet/README.md @@ -4,7 +4,7 @@ This blueprint presents an opinionated architecture to handle multiple homogeneo The pattern used in this design is useful, for blueprint, in cases where multiple clusters host/support the same workloads, such as in the case of a multi-regional deployment. Furthermore, combined with Anthos Config Sync and proper RBAC, this architecture can be used to host multiple tenants (e.g. teams, applications) sharing the clusters. -This blueprint is used as part of the [FAST GKE stage](../../../fast/stages/03-gke-multitenant/) but it can also be used independently if desired. +This blueprint is used as part of the [FAST GKE stage](../../../fast/stages/3-gke-multitenant/) but it can also be used independently if desired.
diff --git a/blueprints/networking/hub-and-spoke-vpn/README.md b/blueprints/networking/hub-and-spoke-vpn/README.md index 5f596142f0..bdf877c737 100644 --- a/blueprints/networking/hub-and-spoke-vpn/README.md +++ b/blueprints/networking/hub-and-spoke-vpn/README.md @@ -7,7 +7,7 @@ A few additional features are also shown: - [custom BGP advertisements](https://cloud.google.com/router/docs/how-to/advertising-overview) to implement transitivity between spokes - [VPC Global Routing](https://cloud.google.com/network-connectivity/docs/router/how-to/configuring-routing-mode) to leverage a regional set of VPN gateways in different regions as next hops (used here for illustrative/study purpose, not usually done in real life) -The blueprint has been purposefully kept simple to show how to use and wire the VPC and VPN-HA modules together, and so that it can be used as a basis for experimentation. For a more complex scenario that better reflects real-life usage, including [Shared VPC](https://cloud.google.com/vpc/docs/shared-vpc) and [DNS cross-project binding](https://cloud.google.com/dns/docs/zones/cross-project-binding) please refer to the [FAST network stage](../../../fast/stages/02-networking-vpn/). +The blueprint has been purposefully kept simple to show how to use and wire the VPC and VPN-HA modules together, and so that it can be used as a basis for experimentation. For a more complex scenario that better reflects real-life usage, including [Shared VPC](https://cloud.google.com/vpc/docs/shared-vpc) and [DNS cross-project binding](https://cloud.google.com/dns/docs/zones/cross-project-binding) please refer to the [FAST network stage](../../../fast/stages/2-networking-b-vpn/). This is the high level diagram of this blueprint: diff --git a/diagram.svg b/diagram.svg new file mode 100644 index 0000000000..689adf24eb --- /dev/null +++ b/diagram.svg @@ -0,0 +1,293 @@ + \ No newline at end of file diff --git a/fast/README.md b/fast/README.md index e35a483728..7459c870ca 100644 --- a/fast/README.md +++ b/fast/README.md @@ -12,7 +12,7 @@ Fabric FAST was initially conceived to help enterprises quickly set up a GCP org ### Contracts and stages -FAST uses the concept of stages, which individually perform precise tasks but, taken together, build a functional, ready-to-use GCP organization. More importantly, stages are modeled around the security boundaries that typically appear in mature organizations. This arrangement allows delegating ownership of each stage to the team responsible for the types of resources it manages. For example, as its name suggests, the networking stage sets up all the networking elements and is usually the responsibility of a dedicated networking team within the organization. +FAST uses the concept of stages, which individually perform precise tasks but taken together build a functional, ready-to-use GCP organization. More importantly, stages are modeled around the security boundaries that typically appear in mature organizations. This arrangement allows delegating ownership of each stage to the team responsible for the types of resources it manages. For example, as its name suggests, the networking stage sets up all the networking elements and is usually the responsibility of a dedicated networking team within the organization. From the perspective of FAST's overall design, stages also work as contacts or interfaces, defining a set of pre-requisites and inputs required to perform their designed task and generating outputs needed by other stages lower in the chain. The diagram below shows the relationships between stages. @@ -20,7 +20,7 @@ From the perspective of FAST's overall design, stages also work as contacts or i
-Please refer to the [stages](./stages/) section for further details on each stage. +Please refer to the [stages](./stages/) section for further details on each stage. For details on tenant-level stages which introduce a deeper level of autonomy via nested FAST setups rooted in a top-level folder, refer to the [multitenant stages](#multitenant-organizations) section below. ### Security-first design @@ -32,11 +32,21 @@ FAST also aims to minimize the number of permissions granted to principals accor A resource factory consumes a simple representation of a resource (e.g., in YAML) and deploys it (e.g., using Terraform). Used correctly, factories can help decrease the management overhead of large-scale infrastructure deployments. See "[Resource Factories: A descriptive approach to Terraform](https://medium.com/google-cloud/resource-factories-a-descriptive-approach-to-terraform-581b3ebb59c)" for more details and the rationale behind factories. -FAST uses YAML-based factories to deploy subnets and firewall rules and, as its name suggests, in the [project factory](./stages/03-project-factory/) stage. +FAST uses YAML-based factories to deploy subnets and firewall rules and, as its name suggests, in the [project factory](./stages/3-project-factory/) stage. ### CI/CD -One of our objectives with FAST is to provide a lightweight reference design for the IaC repositories, and a built-in implementation for running our code in automated pipelines. Our CI/CD approach leverages [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation), and provides sample workflow configurations for several major providers. Refer to the [CI/CD section in the bootstrap stage](stages/00-bootstrap/README.md#cicd) for more details. We also provide separate [optional small stages](./extras/) to help you configure your CI/CD provider. +One of our objectives with FAST is to provide a lightweight reference design for the IaC repositories, and a built-in implementation for running our code in automated pipelines. Our CI/CD approach leverages [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation), and provides sample workflow configurations for several major providers. Refer to the [CI/CD section in the bootstrap stage](./stages/0-bootstrap/README.md#cicd) for more details. We also provide separate [optional small stages](./extras/) to help you configure your CI/CD provider. + +### Multitenant organizations + +FAST has built-in support for complex multitenant organizations, where each tenant has complete control over a separate hierarchy rooted in a top-level folder. This approach is particularly suited for large enterprises or governments, where country-level subsidiaries or government agencies have a wide degree of autonomy within a shared GCP organization managed by a central entity. + +FAST implements multitenancy via [dedicated stages](stages-multitenant) for tenant-level bootstrap and resource management, which configure separate hierarchies within the organization rooted in top-level folders, so that subsequent FAST stages (networking, security, data, etc.) can be used directly for each tenant. The diagram below shows the relationships between organization-level and tenant-level stages. + ++ +
## Implementation @@ -57,9 +67,9 @@ Those familiar with Python will note that FAST follows many of the maxims in the ## Roadmap -Besides the features already described, FAST roadmap includes: +Besides the features already described, FAST also includes: - Stage to deploy environment-specific multitenant GKE clusters following Google's best practices - Stage to deploy a fully featured data platform -- Reference implementation to use FAST in CI/CD pipelines (in progress) -- Static policy enforcement +- Reference implementation to use FAST in CI/CD pipelines +- Static policy enforcement (planned) diff --git a/fast/extras/0-cicd-github/README.md b/fast/extras/0-cicd-github/README.md new file mode 100644 index 0000000000..58407b5e4e --- /dev/null +++ b/fast/extras/0-cicd-github/README.md @@ -0,0 +1,139 @@ +# FAST GitHub repository management + +This small extra stage allows creating and populating GitHub repositories used to host FAST stage code, including rewriting of module sources and secrets used for private modules repository access. + +It is designed for use in a GitHub organization, and is only meant as a one-shot solution with perishable state especially when used for initial population, as you don't want Terraform to keep overwriting your changes with initial versions of files. + +Initial population is only meant to be used with actual stage, while populating the modules repository should be done by hand to avoid hitting the GitHub hourly limit for their API. + +Once initial population is done, you need to manually push to the repository + +- the `.tfvars` file with custom variable values for your stages +- the workflow configuration file generated by FAST stages + +## GitHub provider credentials + +A [GitHub token](https://github.com/settings/tokens) is needed to authenticate against their API. The token needs organization-level permissions, like shown in this screenshot: + ++ +
+ +Once a token is available set it in the `GITHUB_TOKEN` environment variable before running Terraform. + +## Variable configuration + +The `organization` required variable sets the GitHub organization where repositories will be created, and is used to configure the Terraform provider. + +### Modules repository and sources + +The `modules_config` variable controls creation and management of the key and secret used to access the private modules repository, and indirectly control population of initial files: if the `modules_config` variable is not specified no module repository is know to the code, so module source paths cannot be replaced, and initial population of files cannot happen. If the variable is specified, an optional `source_ref` attribute can be set to the reference used to pin modules versions. + +This is an example that configures the modules repository name and an optional reference, enabling initial population of repositories where the feature has been turned on: + +```hcl +modules_config = { + repository_name = "GoogleCloudPlatform/cloud-foundation-fabric" + source_ref = "v19.0.0" +} +# tftest skip +``` + +In the above example, no key options are set so it's assumed modules will be fetched from a public repository. If modules repository authentication is needed the `key_config` attribute also needs to be set. + +If no keypair path is specified an internally generated key will be stored as an access key in the modules repository, and as secrets in the stage repositories: + +```hcl +modules_config = { + repository_name = "GoogleCloudPlatform/cloud-foundation-fabric" + key_config = { + create_key = true + create_secrets = true + } +} +# tftest skip +``` + +To use an existing keypair pass the path to the private key, the public key name is assumed to have the same name ending with the `.pub` suffix. This is useful in cases where the access key has already been set in the modules repository, and new repositories need to be created and their corresponding secret set: + +```hcl +modules_config = { + repository_name = "GoogleCloudPlatform/cloud-foundation-fabric" + key_config = { + create_secrets = true + keypair_path = "~/modules-repository-key" + } +} +# tftest skip +``` + +### Repositories + +The `repositories` variable is where you configure which repositories to create and whether initial population of files is desired. + +This is an example that creates repositories for stages 00 and 01, and populates initial files for stages 00, 01, and 02: + +```tfvars +repositories = { + fast_00_bootstrap = { + create_options = { + description = "FAST bootstrap." + features = { + issues = true + } + } + populate_from = "../../stages/0-bootstrap" + } + fast_01_resman = { + create_options = { + description = "FAST resource management." + features = { + issues = true + } + } + populate_from = "../../stages/1-resman" + } + fast_02_networking = { + populate_from = "../../stages/2-networking-peering" + } +} +# tftest skip +``` + +The `create_options` repository attribute controls creation: if the attribute is not present, the repository is assumed to be already existing. + +Initial population depends on a modules repository being configured in the `modules_config` variable described in the preceding section and on the`populate_from` attributes in each repository where population is required, which point to the folder holding the files to be committed. + +### Commit configuration + +Finally, a `commit_config` variable is optional: it can be used to configure author, email and message used in commits for initial population of files, its defaults are probably fine for most use cases. + + + + +## Files + +| name | description | resources | +|---|---|---| +| [cicd-versions.tf](./cicd-versions.tf) | Provider version. | | +| [main.tf](./main.tf) | Module-level locals and resources. |github_actions_secret
· github_repository
· github_repository_deploy_key
· github_repository_file
· tls_private_key
|
+| [outputs.tf](./outputs.tf) | Module outputs. | |
+| [providers.tf](./providers.tf) | Provider configuration. | |
+| [variables.tf](./variables.tf) | Module variables. | |
+
+## Variables
+
+| name | description | type | required | default |
+|---|---|:---:|:---:|:---:|
+| [organization](variables.tf#L50) | GitHub organization. | string
| ✓ | |
+| [commmit_config](variables.tf#L17) | Configure commit metadata. | object({…})
| | {}
|
+| [modules_config](variables.tf#L28) | Configure access to repository module via key, and replacement for modules sources in stage repositories. | object({…})
| | null
|
+| [repositories](variables.tf#L55) | Repositories to create. | map(object({…}))
| | {}
|
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| [clone](outputs.tf#L17) | Clone repository commands. | |
+
+
diff --git a/fast/extras/00-cicd-github/cicd-versions.tf b/fast/extras/0-cicd-github/cicd-versions.tf
similarity index 96%
rename from fast/extras/00-cicd-github/cicd-versions.tf
rename to fast/extras/0-cicd-github/cicd-versions.tf
index 09f544cba0..830f1e48a3 100644
--- a/fast/extras/00-cicd-github/cicd-versions.tf
+++ b/fast/extras/0-cicd-github/cicd-versions.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/fast/extras/00-cicd-github/github_token.png b/fast/extras/0-cicd-github/github_token.png
similarity index 100%
rename from fast/extras/00-cicd-github/github_token.png
rename to fast/extras/0-cicd-github/github_token.png
diff --git a/fast/extras/00-cicd-github/main.tf b/fast/extras/0-cicd-github/main.tf
similarity index 72%
rename from fast/extras/00-cicd-github/main.tf
rename to fast/extras/0-cicd-github/main.tf
index ac6028c17e..d91ab970c5 100644
--- a/fast/extras/00-cicd-github/main.tf
+++ b/fast/extras/0-cicd-github/main.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,9 +15,6 @@
*/
locals {
- _modules_repository = [
- for k, v in var.repositories : local.repositories[k] if v.has_modules
- ]
_repository_files = flatten([
for k, v in var.repositories : [
for f in concat(
@@ -30,12 +27,12 @@ locals {
}
] if v.populate_from != null
])
- modules_ref = var.modules_ref == null ? "" : "?ref=${var.modules_ref}"
- modules_repository = (
- length(local._modules_repository) > 0
- ? local._modules_repository.0
- : null
+ modules_ref = (
+ try(var.modules_config.source_ref, null) == null
+ ? ""
+ : "?ref=${var.modules_config.source_ref}"
)
+ modules_repo = try(var.modules_config.repository_name, null)
repositories = {
for k, v in var.repositories :
k => v.create_options == null ? k : github_repository.default[k].name
@@ -56,6 +53,15 @@ locals {
name = "templates/providers.tf.tpl"
}
if v.populate_from != null
+ },
+ {
+ for k, v in var.repositories :
+ "${k}/templates/workflow-github.yaml" => {
+ repository = k
+ file = "../../assets/templates/workflow-github.yaml"
+ name = "templates/workflow-github.yaml"
+ }
+ if v.populate_from != null
}
)
}
@@ -96,41 +102,49 @@ resource "github_repository" "default" {
}
resource "tls_private_key" "default" {
- count = local.modules_repository != null ? 1 : 0
algorithm = "ED25519"
}
resource "github_repository_deploy_key" "default" {
- count = local.modules_repository == null ? 0 : 1
+ count = (
+ try(var.modules_config.key_config.create_key, null) == true ? 1 : 0
+ )
title = "Modules repository access"
- repository = local.modules_repository
- key = tls_private_key.default.0.public_key_openssh
- read_only = true
+ repository = local.modules_repo
+ key = (
+ try(var.modules_config.key_config.keypair_path, null) == null
+ ? tls_private_key.default.public_key_openssh
+ : file(pathexpand("${var.modules_config.key_config.keypair_path}.pub"))
+ )
+ read_only = true
}
resource "github_actions_secret" "default" {
- for_each = local.modules_repository == null ? {} : {
- for k, v in local.repositories :
- k => v if k != local.modules_repository
- }
- repository = each.key
- secret_name = "CICD_MODULES_KEY"
- plaintext_value = tls_private_key.default.0.private_key_openssh
+ for_each = (
+ try(var.modules_config.key_config.create_secrets, null) == true
+ ? local.repositories
+ : {}
+ )
+ repository = each.key
+ secret_name = "CICD_MODULES_KEY"
+ plaintext_value = (
+ try(var.modules_config.key_config.keypair_path, null) == null
+ ? tls_private_key.default.private_key_openssh
+ : file(pathexpand("${var.modules_config.key_config.keypair_path}"))
+ )
}
resource "github_repository_file" "default" {
- for_each = (
- local.modules_repository == null ? {} : local.repository_files
- )
+ for_each = local.modules_repo == null ? {} : local.repository_files
repository = local.repositories[each.value.repository]
branch = "main"
file = each.value.name
content = (
- endswith(each.value.name, ".tf") && local.modules_repository != null
+ endswith(each.value.name, ".tf") && local.modules_repo != null
? replace(
file(each.value.file),
"/source\\s*=\\s*\"../../../modules/([^/\"]+)\"/",
- "source = \"git@github.com:${var.organization}/${local.modules_repository}.git//$1${local.modules_ref}\"" # "
+ "source = \"git@github.com:${local.modules_repo}.git//$1${local.modules_ref}\"" # "
)
: file(each.value.file)
)
diff --git a/fast/extras/00-cicd-github/outputs.tf b/fast/extras/0-cicd-github/outputs.tf
similarity index 96%
rename from fast/extras/00-cicd-github/outputs.tf
rename to fast/extras/0-cicd-github/outputs.tf
index cb580e1fe2..61b5ffbc77 100644
--- a/fast/extras/00-cicd-github/outputs.tf
+++ b/fast/extras/0-cicd-github/outputs.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/fast/extras/00-cicd-github/providers.tf b/fast/extras/0-cicd-github/providers.tf
similarity index 95%
rename from fast/extras/00-cicd-github/providers.tf
rename to fast/extras/0-cicd-github/providers.tf
index 29be30ae98..a7ccb32d4c 100644
--- a/fast/extras/00-cicd-github/providers.tf
+++ b/fast/extras/0-cicd-github/providers.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/fast/extras/00-cicd-github/variables.tf b/fast/extras/0-cicd-github/variables.tf
similarity index 75%
rename from fast/extras/00-cicd-github/variables.tf
rename to fast/extras/0-cicd-github/variables.tf
index 0d9cb7fd6d..8e5d0832ff 100644
--- a/fast/extras/00-cicd-github/variables.tf
+++ b/fast/extras/0-cicd-github/variables.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,10 +25,26 @@ variable "commmit_config" {
nullable = false
}
-variable "modules_ref" {
- description = "Optional git ref used in module sources."
- type = string
- default = null
+variable "modules_config" {
+ description = "Configure access to repository module via key, and replacement for modules sources in stage repositories."
+ type = object({
+ repository_name = string
+ source_ref = optional(string)
+ key_config = optional(object({
+ create_key = optional(bool, false)
+ create_secrets = optional(bool, false)
+ keypair_path = optional(string)
+ }), {})
+ })
+ default = null
+ validation {
+ condition = (
+ var.modules_config == null
+ ||
+ try(var.modules_config.repository_name, null) != null
+ )
+ error_message = "Modules configuration requires a modules repository name."
+ }
}
variable "organization" {
@@ -63,7 +79,6 @@ variable "repositories" {
}), {})
visibility = optional(string, "private")
}))
- has_modules = optional(bool, false)
populate_from = optional(string)
}))
default = {}
diff --git a/fast/extras/00-cicd-github/README.md b/fast/extras/00-cicd-github/README.md
deleted file mode 100644
index acf249bcfd..0000000000
--- a/fast/extras/00-cicd-github/README.md
+++ /dev/null
@@ -1,105 +0,0 @@
-# FAST GitHub repository management
-
-This small extra stage allows creation and management of GitHub repositories used to host FAST stage code, including initial population of files and rewriting of module sources.
-
-This stage is designed for quick repository creation in a GitHub organization, and is not suited for medium or long-term repository management especially if you enable initial population of files.
-
-## Initial population caveats
-
-Initial file population of repositories is controlled via the `populate_from` attribute, and needs a bit of care:
-
-- never run this stage with the same variables used for population once the repository starts being used, as **Terraform will manage file state and revert any changes at each apply**, which is probably not what you want.
-- initial population of the modules repository is discouraged, as the number of resulting files Terraform needs to manage is very close to the GitHub hourly limit for their API, it's much easier to populate modules via regular git commands
-
-The scenario for which this stage has been designed is one-shot creation and/or population of stage repositories, running it multiple times with different variables and Terraform states if incremental creation is needed for subsequent FAST stages (e.g. GKE, data platform, etc.).
-
-Once initial population is done, you need to manually push to the repository
-
-- the `.tfvars` file with custom variable values for your stages
-- the workflow configuration file generated by FAST stages
-
-## GitHub provider credentials
-
-A [GitHub token](https://github.com/settings/tokens) is needed to authenticate against their API. The token needs organization-level permissions, like shown in this screenshot:
-
-- -
- -## Variable configuration - -The `organization` required variable sets the GitHub organization where repositories will be created, and is used to configure the Terraform provider. - -The `repositories` variable is where you configure which repositories to create, whether initial population of files is desired, and which repository is used to host modules. - -This is an example that creates repositories for stages 00 and 01, defines an existing repositories as the source for modules, and populates initial files for stages 00, 01, and 02: - -```tfvars -organization = "ludomagno" -repositories = { - fast_00_bootstrap = { - create_options = { - description = "FAST bootstrap." - features = { - issues = true - } - } - populate_from = "../../stages/00-bootstrap" - } - fast_01_resman = { - create_options = { - description = "FAST resource management." - features = { - issues = true - } - } - populate_from = "../../stages/01-resman" - } - fast_02_networking = { - populate_from = "../../stages/02-networking-peering" - } - fast_modules = { - has_modules = true - } -} -``` - -The `create_options` repository attribute controls creation: if the attribute is not present, the repository is assumed to be already existing. - -Initial population depends on a modules repository being configured, identified by the `has_modules` attribute, and on `populate_from` attributes in each repository where population is required, pointing to the folder holding the files to be committed. - -Finally, a `commit_config` variable is optional: it can be used to configure author, email and message used in commits for initial population of files, its defaults are probably fine for most use cases. - -## Modules secret - -When initial population is configured for a repository, this stage also adds a secret with the private key used to authenticate against the modules repository. This matches the configuration of the GitHub workflow files created for each FAST stage when CI/CD is enabled. - - - - -## Files - -| name | description | resources | -|---|---|---| -| [cicd-versions.tf](./cicd-versions.tf) | Provider version. | | -| [main.tf](./main.tf) | Module-level locals and resources. |github_actions_secret
· github_repository
· github_repository_deploy_key
· github_repository_file
· tls_private_key
|
-| [outputs.tf](./outputs.tf) | Module outputs. | |
-| [providers.tf](./providers.tf) | Provider configuration. | |
-| [variables.tf](./variables.tf) | Module variables. | |
-
-## Variables
-
-| name | description | type | required | default |
-|---|---|:---:|:---:|:---:|
-| [organization](variables.tf#L34) | GitHub organization. | string
| ✓ | |
-| [commmit_config](variables.tf#L17) | Configure commit metadata. | object({…})
| | {}
|
-| [modules_ref](variables.tf#L28) | Optional git ref used in module sources. | string
| | null
|
-| [repositories](variables.tf#L39) | Repositories to create. | map(object({…}))
| | {}
|
-
-## Outputs
-
-| name | description | sensitive |
-|---|---|:---:|
-| [clone](outputs.tf#L17) | Clone repository commands. | |
-
-
diff --git a/fast/extras/README.md b/fast/extras/README.md
index 121fa4b049..9213224cda 100644
--- a/fast/extras/README.md
+++ b/fast/extras/README.md
@@ -2,4 +2,4 @@
This folder contains additional helper stages for FAST, which can be used to simplify specific operational tasks:
-- [GitHub repository management](./00-cicd-github/)
+- [GitHub repository management](./0-cicd-github/)
diff --git a/fast/stage-links.sh b/fast/stage-links.sh
new file mode 100755
index 0000000000..cfa7919d17
--- /dev/null
+++ b/fast/stage-links.sh
@@ -0,0 +1,114 @@
+#!/bin/bash
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+if [ $# -eq 0 ]; then
+ echo "Error: no folder or GCS bucket specified. Use -h or --help for usage."
+ exit 1
+fi
+
+if [[ "$1" == "-h" || "$1" == "--help" ]]; then
+ cat <+
additive, •
conditional.
+
+## Organization [org_id #0]
+
+| members | roles |
+|---|---|
+|tn0-admins+
•
+
|
+|tn0-gke-dev-0+
•
|
+|tn0-gke-prod-0+
•
|
+|tn0-networking-0+
•
|
+|tn0-pf-dev-0+
•
|
+|tn0-pf-prod-0+
•
|
+|tn0-resman-0+
•
|
+|tn0-sandbox-0+
•
|
+|tn0-security-0+
•
|
+|tn0-teams-0+
•
|
+
+## Folder test tenant 0 [#1]
+
+| members | roles |
+|---|---|
+|tn0-admins+
|
+
+## Project tn0-audit-logs-0
+
+| members | roles |
+|---|---|
+|f260055713332-284719+
•
|
+|prod-resman-0+
|
+|prod-resman-0iam-service-account
| google_organization_iam_member
|
+| [automation.tf](./automation.tf) | Tenant automation project and resources. | gcs
· iam-service-account
· project
| |
+| [billing.tf](./billing.tf) | Billing roles for standalone billing accounts. | | google_billing_account_iam_member
|
+| [cicd.tf](./cicd.tf) | Workload Identity Federation configurations for CI/CD. | iam-service-account
· source-repository
| |
+| [identity-providers.tf](./identity-providers.tf) | Workload Identity Federation provider definitions. | | google_iam_workload_identity_pool
· google_iam_workload_identity_pool_provider
|
+| [log-export.tf](./log-export.tf) | Audit log project and sink. | bigquery-dataset
· gcs
· logging-bucket
· project
· pubsub
| |
+| [main.tf](./main.tf) | Module-level locals and resources. | folder
| |
+| [organization.tf](./organization.tf) | Organization tag and conditional IAM grant. | organization
| google_organization_iam_member
· google_tags_tag_value_iam_member
|
+| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | local_file
|
+| [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | google_storage_bucket_object
|
+| [outputs.tf](./outputs.tf) | Module outputs. | | |
+| [variables.tf](./variables.tf) | Module variables. | | |
+
+## Variables
+
+| name | description | type | required | default | producer |
+|---|---|:---:|:---:|:---:|:---:|
+| [automation](variables.tf#L20) | Automation resources created by the organization-level bootstrap stage. | object({…})
| ✓ | | 0-bootstrap
|
+| [billing_account](variables.tf#L38) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…})
| ✓ | | 0-bootstrap
|
+| [organization](variables.tf#L193) | Organization details. | object({…})
| ✓ | | 0-bootstrap
|
+| [prefix](variables.tf#L209) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 0-bootstrap
|
+| [tag_keys](variables.tf#L232) | Organization tag keys. | object({…})
| ✓ | | 1-resman
|
+| [tag_names](variables.tf#L243) | Customized names for resource management tags. | object({…})
| ✓ | | 1-resman
|
+| [tag_values](variables.tf#L254) | Organization resource management tag values. | map(string)
| ✓ | | 1-resman
|
+| [tenant_config](variables.tf#L261) | Tenant configuration. Short name must be 4 characters or less. | object({…})
| ✓ | | |
+| [cicd_repositories](variables.tf#L51) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…})
| | null
| |
+| [custom_roles](variables.tf#L97) | Custom roles defined at the organization level, in key => id format. | object({…})
| | null
| 0-bootstrap
|
+| [fast_features](variables.tf#L106) | Selective control for top-level FAST features. | object({…})
| | {}
| 0-bootstrap
|
+| [federated_identity_providers](variables.tf#L120) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…}))
| | {}
| |
+| [group_iam](variables.tf#L134) | Tenant-level custom group IAM settings in group => [roles] format. | map(list(string))
| | {}
| |
+| [iam](variables.tf#L140) | Tenant-level custom IAM settings in role => [principal] format. | map(list(string))
| | {}
| |
+| [iam_additive](variables.tf#L146) | Tenant-level custom IAM settings in role => [principal] format for non-authoritative bindings. | map(list(string))
| | {}
| |
+| [locations](variables.tf#L152) | Optional locations for GCS, BigQuery, and logging buckets created here. These are the defaults set at the organization level, and can be overridden via the tenant config variable. | object({…})
| | {…}
| 0-bootstrap
|
+| [log_sinks](variables.tf#L172) | Tenant-level log sinks, in name => {type, filter} format. | map(object({…}))
| | {…}
| |
+| [outputs_location](variables.tf#L203) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string
| | null
| |
+| [project_parent_ids](variables.tf#L219) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the tenant folder as parent. | object({…})
| | {…}
| |
+| [test_principal](variables.tf#L301) | Used when testing to bypass the data source returning the current identity. | string
| | null
| |
+
+## Outputs
+
+| name | description | sensitive | consumers |
+|---|---|:---:|---|
+| [cicd_workflows](outputs.tf#L102) | CI/CD workflows for tenant bootstrap and resource management stages. | ✓ | |
+| [federated_identity](outputs.tf#L108) | Workload Identity Federation pool and providers. | | |
+| [provider](outputs.tf#L118) | Terraform provider file for tenant resource management stage. | ✓ | stage-01
|
+| [tenant_resources](outputs.tf#L125) | Tenant-level resources. | | |
+| [tfvars](outputs.tf#L136) | Terraform variable files for the following tenant stages. | ✓ | |
+
+
diff --git a/fast/stages-multitenant/0-bootstrap-tenant/automation-sas.tf b/fast/stages-multitenant/0-bootstrap-tenant/automation-sas.tf
new file mode 100644
index 0000000000..c548f8d5fa
--- /dev/null
+++ b/fast/stages-multitenant/0-bootstrap-tenant/automation-sas.tf
@@ -0,0 +1,127 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Tenant automation stage 2 and 3 service accounts.
+
+locals {
+ branch_sas = {
+ dp-dev = {
+ condition = join(" && ", [
+ "resource.matchTag('${local.tag_keys.context}', 'data')",
+ "resource.matchTag('${local.tag_keys.environment}', 'development')"
+ ])
+ description = "data platform dev"
+ flag = "data_platform"
+ }
+ dp-prod = {
+ condition = join(" && ", [
+ "resource.matchTag('${local.tag_keys.context}', 'data')",
+ "resource.matchTag('${local.tag_keys.environment}', 'production')"
+ ])
+ description = "data platform prod"
+ flag = "data_platform"
+ }
+ gke-dev = {
+ condition = join(" && ", [
+ "resource.matchTag('${local.tag_keys.context}', 'gke')",
+ "resource.matchTag('${local.tag_keys.environment}', 'development')"
+ ])
+ description = "GKE dev"
+ flag = "gke"
+ }
+ gke-prod = {
+ condition = join(" && ", [
+ "resource.matchTag('${local.tag_keys.context}', 'gke')",
+ "resource.matchTag('${local.tag_keys.environment}', 'production')"
+ ])
+ description = "GKE prod"
+ flag = "gke"
+ }
+ networking = {
+ condition = "resource.matchTag('${local.tag_keys.context}', 'networking')"
+ description = "networking"
+ flag = "-"
+ }
+ pf-dev = {
+ condition = "resource.matchTag('${local.tag_keys.environment}', 'development')"
+ description = "project factory dev"
+ flag = "project_factory"
+ }
+ pf-prod = {
+ condition = "resource.matchTag('${local.tag_keys.environment}', 'production')"
+ description = "project factory prod"
+ flag = "project_factory"
+ }
+ sandbox = {
+ condition = "resource.matchTag('${local.tag_keys.context}', 'sandbox')"
+ description = "sandbox"
+ flag = "sandbox"
+ }
+ security = {
+ condition = "resource.matchTag('${local.tag_keys.context}', 'security')"
+ description = "security"
+ flag = "-"
+ }
+ teams = {
+ condition = "resource.matchTag('${local.tag_keys.context}', 'teams')"
+ description = "teams"
+ flag = "teams"
+ }
+ }
+}
+
+module "automation-tf-resman-sa-stage2-3" {
+ source = "../../../modules/iam-service-account"
+ for_each = {
+ for k, v in local.branch_sas :
+ k => v if lookup(local.fast_features, v.flag, true)
+ }
+ project_id = module.automation-project.project_id
+ name = "${each.key}-0"
+ display_name = "Terraform ${each.value.description} service account."
+ prefix = local.prefix
+ iam_billing_roles = !var.billing_account.is_org_level ? {
+ (var.billing_account.id) = [
+ "roles/billing.user", "roles/billing.costsManager"
+ ]
+ } : {}
+ iam_organization_roles = var.billing_account.is_org_level ? {
+ (var.organization.id) = [
+ "roles/billing.user", "roles/billing.costsManager"
+ ]
+ } : {}
+}
+
+# assign org policy admin with a tag-based condition to stage 2 and 3 SAs
+
+resource "google_organization_iam_member" "org_policy_admin_stage2_3" {
+ for_each = {
+ for k, v in module.automation-tf-resman-sa-stage2-3 : k => v.iam_email
+ }
+ org_id = var.organization.id
+ role = "roles/orgpolicy.policyAdmin"
+ member = each.value
+ condition {
+ title = "org_policy_tag_${var.tenant_config.short_name}_${each.key}_scoped"
+ description = join("", [
+ "Org policy tag scoped grant for tenant ${var.tenant_config.short_name} ",
+ local.branch_sas[each.key].description
+ ])
+ expression = join(" && ", [
+ local.iam_tenant_condition, local.branch_sas[each.key].condition
+ ])
+ }
+}
diff --git a/fast/stages-multitenant/0-bootstrap-tenant/automation.tf b/fast/stages-multitenant/0-bootstrap-tenant/automation.tf
new file mode 100644
index 0000000000..9684e7ca3e
--- /dev/null
+++ b/fast/stages-multitenant/0-bootstrap-tenant/automation.tf
@@ -0,0 +1,141 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Tenant automation project and resources.
+
+module "automation-project" {
+ source = "../../../modules/project"
+ billing_account = var.billing_account.id
+ name = "iac-core-0"
+ parent = coalesce(
+ var.project_parent_ids.automation,
+ module.tenant-folder.id
+ )
+ prefix = local.prefix
+ # human (groups) IAM bindings
+ group_iam = {
+ (local.groups.gcp-admins) = [
+ "roles/iam.serviceAccountAdmin",
+ "roles/iam.serviceAccountTokenCreator",
+ ]
+ (local.groups.gcp-admins) = [
+ "roles/iam.serviceAccountTokenCreator",
+ "roles/iam.workloadIdentityPoolAdmin"
+ ]
+ }
+ # machine (service accounts) IAM bindings
+ iam = {
+ "roles/owner" = [
+ module.automation-tf-resman-sa.iam_email,
+ "serviceAccount:${local.resman_sa}"
+ ]
+ "roles/cloudbuild.builds.editor" = [
+ module.automation-tf-resman-sa.iam_email
+ ]
+ "roles/iam.serviceAccountAdmin" = [
+ module.automation-tf-resman-sa.iam_email
+ ]
+ "roles/iam.workloadIdentityPoolAdmin" = [
+ module.automation-tf-resman-sa.iam_email
+ ]
+ "roles/source.admin" = [
+ module.automation-tf-resman-sa.iam_email
+ ]
+ "roles/storage.admin" = [
+ module.automation-tf-resman-sa.iam_email
+ ]
+ }
+ services = [
+ "accesscontextmanager.googleapis.com",
+ "bigquery.googleapis.com",
+ "bigqueryreservation.googleapis.com",
+ "bigquerystorage.googleapis.com",
+ "billingbudgets.googleapis.com",
+ "cloudbilling.googleapis.com",
+ "cloudbuild.googleapis.com",
+ "cloudkms.googleapis.com",
+ "cloudresourcemanager.googleapis.com",
+ "container.googleapis.com",
+ "compute.googleapis.com",
+ "container.googleapis.com",
+ "essentialcontacts.googleapis.com",
+ "iam.googleapis.com",
+ "iamcredentials.googleapis.com",
+ "orgpolicy.googleapis.com",
+ "pubsub.googleapis.com",
+ "servicenetworking.googleapis.com",
+ "serviceusage.googleapis.com",
+ "sourcerepo.googleapis.com",
+ "stackdriver.googleapis.com",
+ "storage-component.googleapis.com",
+ "storage.googleapis.com",
+ "sts.googleapis.com"
+ ]
+}
+
+# output files bucket
+
+module "automation-tf-output-gcs" {
+ source = "../../../modules/gcs"
+ project_id = module.automation-project.project_id
+ name = "iac-core-outputs-0"
+ prefix = local.prefix
+ location = local.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+}
+
+# resource management stage bucket and service account
+
+module "automation-tf-resman-gcs" {
+ source = "../../../modules/gcs"
+ project_id = module.automation-project.project_id
+ name = "iac-core-resman-0"
+ prefix = local.prefix
+ location = local.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [module.automation-tf-resman-sa.iam_email]
+ }
+}
+
+module "automation-tf-resman-sa" {
+ source = "../../../modules/iam-service-account"
+ project_id = module.automation-project.project_id
+ name = "resman-0"
+ display_name = "Terraform stage 1 resman service account."
+ prefix = local.prefix
+ # allow SA used by CI/CD workflow to impersonate this SA
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.automation-tf-cicd-sa-resman["0"].iam_email, null)
+ ])
+ }
+ iam_billing_roles = !var.billing_account.is_org_level ? {
+ (var.billing_account.id) = [
+ "roles/billing.admin", "roles/billing.costsManager"
+ ]
+ } : {}
+ iam_organization_roles = var.billing_account.is_org_level ? {
+ (var.organization.id) = [
+ "roles/billing.admin", "roles/billing.costsManager"
+ ]
+ } : {}
+ iam_storage_roles = {
+ (module.automation-tf-output-gcs.name) = ["roles/storage.admin"]
+ }
+}
diff --git a/fast/stages-multitenant/0-bootstrap-tenant/billing.tf b/fast/stages-multitenant/0-bootstrap-tenant/billing.tf
new file mode 100644
index 0000000000..77c26b919c
--- /dev/null
+++ b/fast/stages-multitenant/0-bootstrap-tenant/billing.tf
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Billing roles for standalone billing accounts.
+
+# service account billing roles are in the SA module in automation.tf
+
+resource "google_billing_account_iam_member" "billing_ext_admin" {
+ for_each = toset(var.billing_account.is_org_level ? [] : [
+ "group:${local.groups.gcp-admins}",
+ module.automation-tf-resman-sa.iam_email
+ ])
+ billing_account_id = var.billing_account.id
+ role = "roles/billing.admin"
+ member = each.key
+}
+
+resource "google_billing_account_iam_member" "billing_ext_cost_manager" {
+ for_each = toset(var.billing_account.is_org_level ? [] : [
+ "group:${local.groups.gcp-admins}",
+ module.automation-tf-resman-sa.iam_email
+ ])
+ billing_account_id = var.billing_account.id
+ role = "roles/billing.costsManager"
+ member = each.key
+}
diff --git a/fast/stages-multitenant/0-bootstrap-tenant/cicd.tf b/fast/stages-multitenant/0-bootstrap-tenant/cicd.tf
new file mode 100644
index 0000000000..a25215af20
--- /dev/null
+++ b/fast/stages-multitenant/0-bootstrap-tenant/cicd.tf
@@ -0,0 +1,223 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Workload Identity Federation configurations for CI/CD.
+
+locals {
+ _file_prefix = "tenants/${var.tenant_config.short_name}"
+ # derive identity pool names from identity providers for easy reference
+ cicd_identity_pools = {
+ for k, v in local.cicd_identity_providers :
+ k => split("/providers/", v.name)[0]
+ }
+ # merge org-level and tenant-level identity providers
+ cicd_identity_providers = merge(
+ var.automation.federated_identity_providers,
+ {
+ for k, v in google_iam_workload_identity_pool_provider.default :
+ k => {
+ issuer = local.identity_providers[k].issuer
+ issuer_uri = local.identity_providers[k].issuer_uri
+ name = v.name
+ principal_tpl = local.identity_providers[k].principal_tpl
+ principalset_tpl = local.identity_providers[k].principalset_tpl
+ }
+ })
+ # filter CI/CD repositories to only keep valid ones
+ cicd_repositories = {
+ for k, v in coalesce(var.cicd_repositories, {}) : k => v
+ if(
+ v != null
+ &&
+ (
+ try(v.type, null) == "sourcerepo"
+ ||
+ contains(
+ keys(local.cicd_identity_providers),
+ coalesce(try(v.identity_provider, null), ":")
+ )
+ )
+ &&
+ fileexists(
+ format("${path.module}/templates/workflow-%s.yaml", try(v.type, ""))
+ )
+ )
+ }
+}
+
+# tenant bootstrap runs in the org scope and uses top-level automation project
+
+module "automation-tf-cicd-repo-bootstrap" {
+ source = "../../../modules/source-repository"
+ for_each = {
+ for k, v in local.cicd_repositories : 0 => v
+ if k == "bootstrap" && try(v.type, null) == "sourcerepo"
+ }
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = [
+ local.resman_sa
+ ]
+ "roles/source.reader" = [
+ module.automation-tf-cicd-sa-bootstrap["0"].iam_email
+ ]
+ }
+ triggers = {
+ "fast-${var.tenant_config.short_name}-0-bootstrap" = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
+ service_account = module.automation-tf-cicd-sa-bootstrap["0"].id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+}
+
+module "automation-tf-cicd-sa-bootstrap" {
+ source = "../../../modules/iam-service-account"
+ for_each = {
+ for k, v in local.cicd_repositories : 0 => v
+ if k == "bootstrap" && try(v.type, null) != null
+ }
+ project_id = var.automation.project_id
+ name = "bootstrap-1"
+ display_name = "Terraform CI/CD ${var.tenant_config.short_name} bootstrap."
+ prefix = local.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {}
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
+
+module "automation-tf-org-resman-sa" {
+ source = "../../../modules/iam-service-account"
+ for_each = {
+ for k, v in local.cicd_repositories : 0 => v
+ if k == "bootstrap" && try(v.type, null) != null
+ }
+ project_id = var.automation.project_id
+ name = local.resman_sa
+ service_account_create = false
+ iam_additive = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.automation-tf-cicd-sa-bootstrap["0"].iam_email, null)
+ ])
+ }
+}
+
+# tenant resman runs in the tenant scope and uses its own automation project
+
+module "automation-tf-cicd-repo-resman" {
+ source = "../../../modules/source-repository"
+ for_each = {
+ for k, v in local.cicd_repositories : 0 => v
+ if k == "resman" && try(v.type, null) == "sourcerepo"
+ }
+ project_id = module.automation-project.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = [
+ module.automation-tf-resman-sa.iam_email
+ ]
+ "roles/source.reader" = [
+ module.automation-tf-cicd-sa-resman["0"].iam_email
+ ]
+ }
+ triggers = {
+ fast-1-resman = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
+ service_account = module.automation-tf-cicd-sa-resman["0"].id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+}
+
+module "automation-tf-cicd-sa-resman" {
+ source = "../../../modules/iam-service-account"
+ for_each = {
+ for k, v in local.cicd_repositories : 0 => v
+ if k == "resman" && try(v.type, null) != null
+ }
+ project_id = module.automation-project.project_id
+ name = "resman-1"
+ display_name = "Terraform CI/CD resman."
+ prefix = local.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {}
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (module.automation-project.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (module.automation-tf-output-gcs.name) = ["roles/storage.objectViewer"]
+ }
+}
diff --git a/fast/stages-multitenant/0-bootstrap-tenant/diagram.svg b/fast/stages-multitenant/0-bootstrap-tenant/diagram.svg
new file mode 100644
index 0000000000..4090c7b092
--- /dev/null
+++ b/fast/stages-multitenant/0-bootstrap-tenant/diagram.svg
@@ -0,0 +1,597 @@
+
++
additive, •
conditional.
+
+## Folder development [#0]
+
+| members | roles |
+|---|---|
+|tn0-gke-dev-0+
+
|
+|tn0-security-0+
|
diff --git a/fast/stages-multitenant/1-resman-tenant/README.md b/fast/stages-multitenant/1-resman-tenant/README.md
new file mode 100644
index 0000000000..ae42fc3057
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/README.md
@@ -0,0 +1,184 @@
+# Tenant resource management
+
+This stage is run for a specific tenant after [tenant bootstrap](../0-bootstrap-tenant/) has successfully created initial resources for the tenant, which is then decoupled from the organization.
+
+It is logically equivalent and almost identical in code to the corresponding [organization resource management stage](../../stages/1-resman/), with a few notable differences:
+
+- the hierarchy is rooted in the tenant top-level folder instead of the organization
+- there's no management of tag values and keys since they organization-level resources (it could be implemented for tenant-specific tags if the need arises)
+- automation service accounts for subsequent stages are configured but not created here (tenant-level bootstrap creates them and assigns organization-level permissions)
+
+The stage runs with a dedicated service account for the tenant, which has no permissions at the organization level except for billing and organization policies, constrained by a condition on the tenant tag.
+
+The following diagram is a high level reference of what this stage manages, showing one hypothetical tenant (additional tenants require additional instances of this stage being deployed):
+
+```mermaid
+%%{init: {'theme':'base'}}%%
+classDiagram
+ Tenant_root~📁~ -- tn0_automation
+ Tenant_root~📁~ -- Networking~📁~
+ Tenant_root~📁~ -- Security~📁~
+ Tenant_root~📁~ -- Data_Platform~📁~
+ Data_Platform~📁~ -- DP_Dev~📁~
+ Data_Platform~📁~ -- DP_Prod~📁~
+ Tenant_root~📁~ -- GKE~📁~
+ GKE~📁~ -- GKE_Dev~📁~
+ GKE~📁~ -- GKE_Prod~📁~
+ Tenant_root~📁~ -- Teams~📁~
+ Teams~📁~ -- Team_0~📁~
+ Team_0~📁~ -- Team_0_Dev~📁~
+ Team_0~📁~ -- Team_0_Prod~📁~
+ Tenant_root~📁~ -- Sandbox~📁~
+ class Tenant_root~📁~ {
+ - IAM bindings()
+ - org policies()
+ }
+ class tn0_automation {
+ - GCS buckets
+ - IAM bindings()
+ }
+ class Data_Platform~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class DP_Dev~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class DP_Prod~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class GKE~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class GKE_Dev~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class GKE_Prod~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Networking~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Security~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Sandbox~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Teams~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Team_0~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Team_0_Dev~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+ class Team_0_Prod~📁~ {
+ - IAM bindings()
+ - tag bindings()
+ }
+```
+
+As most of the features of this stage follow the same design and configurations of the [organization-level resource management stage](../../stages/1-resman/), we will only focus on the tenant-specific configuration in this document.
+
+## How to run this stage
+
+As mentioned above this stage is decoupled from organization-level stages: it uses a service account and state bucket from the tenant-specific automation project, and its tfvars and provider files are also tenant-specific.
+
+The `stage-links.sh` script can be used to get the commands needed for the provider and output files, just set the variable for the tenant shortname (the same one specified in the tenant bootstrap stage) and pass a single argument with your FAST output files folder path, or GCS bucket URI:
+
+```bash
+TENANT=tn0 ../../stage-links.sh ~/fast-config
+```
+
+The script output can be copy/pasted to a terminal:
+
+```bash
+# copy and paste the following commands for '1-resman-tenant'
+
+ln -s ~/fast-config/tenants/tn0/providers/1-resman-tenant-providers.tf ./
+ln -s ~/fast-config/tenants/tn0/tfvars/0-bootstrap-tenant.auto.tfvars.json ./
+```
+
+Once that is done, stage-level configuration variables are the same as the corresponding organization-level stage.
+
+### Running the stage
+
+Once the configuration is done just go through the usual `init/apply` cycle. On successful apply, a tfvars file specific for this tenant and a set of provider files will be created.
+
+
+
+
+## Files
+
+| name | description | modules | resources |
+|---|---|---|---|
+| [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder
· gcs
· iam-service-account
| |
+| [branch-gke.tf](./branch-gke.tf) | GKE multitenant stage resources. | folder
· gcs
· iam-service-account
| |
+| [branch-networking.tf](./branch-networking.tf) | Networking stage resources. | folder
· gcs
· iam-service-account
| |
+| [branch-project-factory.tf](./branch-project-factory.tf) | Project factory stage resources. | gcs
· iam-service-account
| |
+| [branch-sandbox.tf](./branch-sandbox.tf) | Sandbox stage resources. | folder
· gcs
| |
+| [branch-security.tf](./branch-security.tf) | Security stage resources. | folder
· gcs
· iam-service-account
| |
+| [branch-teams.tf](./branch-teams.tf) | Team stage resources. | folder
· gcs
· iam-service-account
| |
+| [cicd-data-platform.tf](./cicd-data-platform.tf) | CI/CD resources for the data platform branch. | iam-service-account
· source-repository
| |
+| [cicd-gke.tf](./cicd-gke.tf) | CI/CD resources for the data platform branch. | iam-service-account
· source-repository
| |
+| [cicd-networking.tf](./cicd-networking.tf) | CI/CD resources for the networking branch. | iam-service-account
· source-repository
| |
+| [cicd-project-factory.tf](./cicd-project-factory.tf) | CI/CD resources for the teams branch. | iam-service-account
· source-repository
| |
+| [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | iam-service-account
· source-repository
| |
+| [main.tf](./main.tf) | Module-level locals and resources. | | |
+| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | local_file
|
+| [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | google_storage_bucket_object
|
+| [outputs.tf](./outputs.tf) | Module outputs. | | |
+| [root_node.tf](./root_node.tf) | Tenant root folder configuration. | folder
| |
+| [variables.tf](./variables.tf) | Module variables. | | |
+
+## Variables
+
+| name | description | type | required | default | producer |
+|---|---|:---:|:---:|:---:|:---:|
+| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 0-bootstrap
|
+| [billing_account](variables.tf#L51) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…})
| ✓ | | 0-bootstrap
|
+| [organization](variables.tf#L206) | Organization details. | object({…})
| ✓ | | 0-bootstrap
|
+| [prefix](variables.tf#L228) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 0-bootstrap
|
+| [root_node](variables.tf#L239) | Root folder node for the tenant, in folders/nnnnnn format. | string
| ✓ | | |
+| [short_name](variables.tf#L244) | Short name used to identify the tenant. | string
| ✓ | | |
+| [tags](variables.tf#L249) | Resource management tags. | object({…})
| ✓ | | |
+| [cicd_repositories](variables.tf#L64) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…})
| | null
| |
+| [custom_roles](variables.tf#L146) | Custom roles defined at the org level, in key => id format. | object({…})
| | null
| 0-bootstrap
|
+| [data_dir](variables.tf#L155) | Relative path for the folder storing configuration data. | string
| | "data"
| |
+| [fast_features](variables.tf#L161) | Selective control for top-level FAST features. | object({…})
| | {}
| 0-0-bootstrap
|
+| [groups](variables.tf#L175) | Group names to grant organization-level permissions. | object({…})
| | {}
| 0-bootstrap
|
+| [locations](variables.tf#L188) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…})
| | {…}
| 0-bootstrap
|
+| [organization_policy_data_path](variables.tf#L216) | Path for the data folder used by the organization policies factory. | string
| | null
| |
+| [outputs_location](variables.tf#L222) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string
| | null
| |
+| [team_folders](variables.tf#L267) | Team folders to be created. Format is described in a code comment. | map(object({…}))
| | null
| |
+| [test_skip_data_sources](variables.tf#L277) | Used when testing to bypass data sources. | bool
| | false
| |
+
+## Outputs
+
+| name | description | sensitive | consumers |
+|---|---|:---:|---|
+| [cicd_repositories](outputs.tf#L189) | WIF configuration for CI/CD repositories. | | |
+| [dataplatform](outputs.tf#L203) | Data for the Data Platform stage. | | |
+| [gke_multitenant](outputs.tf#L219) | Data for the GKE multitenant stage. | | 03-gke-multitenant
|
+| [networking](outputs.tf#L240) | Data for the networking stage. | | |
+| [project_factories](outputs.tf#L249) | Data for the project factories stage. | | |
+| [providers](outputs.tf#L264) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking
· 02-security
· 03-dataplatform
· xx-sandbox
· xx-teams
|
+| [sandbox](outputs.tf#L271) | Data for the sandbox stage. | | xx-sandbox
|
+| [security](outputs.tf#L285) | Data for the networking stage. | | 02-security
|
+| [teams](outputs.tf#L295) | Data for the teams stage. | | |
+| [tfvars](outputs.tf#L307) | Terraform variable files for the following stages. | ✓ | |
+
+
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-data-platform.tf b/fast/stages-multitenant/1-resman-tenant/branch-data-platform.tf
new file mode 100644
index 0000000000..3916d63581
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-data-platform.tf
@@ -0,0 +1,133 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Data Platform stages resources.
+
+module "branch-dp-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.data_platform ? 1 : 0
+ parent = module.root-folder.id
+ name = "Data Platform"
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.context}/data"]
+ }
+}
+
+module "branch-dp-dev-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.data_platform ? 1 : 0
+ parent = module.branch-dp-folder.0.id
+ name = "Development"
+ group_iam = {}
+ iam = {
+ (local.custom_roles.service_project_network_admin) = [
+ local.automation_sas_iam.dp-dev
+ ]
+ # remove owner here and at project level if SA does not manage project resources
+ "roles/owner" = [local.automation_sas_iam.dp-dev]
+ "roles/logging.admin" = [local.automation_sas_iam.dp-dev]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.dp-dev]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.dp-dev]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.environment}/development"]
+ }
+}
+
+module "branch-dp-prod-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.data_platform ? 1 : 0
+ parent = module.branch-dp-folder.0.id
+ name = "Production"
+ group_iam = {}
+ iam = {
+ (local.custom_roles.service_project_network_admin) = [
+ local.automation_sas_iam.dp-prod
+ ]
+ # remove owner here and at project level if SA does not manage project resources
+ "roles/owner" = [local.automation_sas_iam.dp-prod]
+ "roles/logging.admin" = [local.automation_sas_iam.dp-prod]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.dp-prod]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.dp-prod]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.environment}/production"]
+ }
+}
+
+# automation service accounts and buckets
+
+module "branch-dp-dev-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.data_platform ? 1 : 0
+ project_id = var.automation.project_id
+ name = "dp-dev-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.branch-dp-dev-sa-cicd.0.iam_email, null)
+ ])
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-dp-prod-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.data_platform ? 1 : 0
+ project_id = var.automation.project_id
+ name = "dp-prod-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.branch-dp-prod-sa-cicd.0.iam_email, null)
+ ])
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-dp-dev-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.data_platform ? 1 : 0
+ project_id = var.automation.project_id
+ name = "dev-resman-dp-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.dp-dev]
+ }
+}
+
+module "branch-dp-prod-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.data_platform ? 1 : 0
+ project_id = var.automation.project_id
+ name = "prod-resman-dp-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.dp-prod]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-gke.tf b/fast/stages-multitenant/1-resman-tenant/branch-gke.tf
new file mode 100644
index 0000000000..9ece810bb8
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-gke.tf
@@ -0,0 +1,133 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description GKE multitenant stage resources.
+
+module "branch-gke-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.gke ? 1 : 0
+ parent = module.root-folder.id
+ name = "GKE"
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.context}/gke"]
+ }
+}
+
+module "branch-gke-dev-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.gke ? 1 : 0
+ parent = module.branch-gke-folder.0.id
+ name = "Development"
+ iam = {
+ "roles/owner" = [local.automation_sas_iam.gke-dev]
+ "roles/logging.admin" = [local.automation_sas_iam.gke-dev]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.gke-dev]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.gke-dev]
+ "roles/compute.xpnAdmin" = [local.automation_sas_iam.gke-dev]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.environment}/development"]
+ }
+}
+
+module "branch-gke-prod-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.gke ? 1 : 0
+ parent = module.branch-gke-folder.0.id
+ name = "Production"
+ iam = {
+ "roles/owner" = [local.automation_sas_iam.gke-prod]
+ "roles/logging.admin" = [local.automation_sas_iam.gke-prod]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.gke-prod]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.gke-prod]
+ "roles/compute.xpnAdmin" = [local.automation_sas_iam.gke-prod]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.environment}/production"]
+ }
+}
+
+module "branch-gke-dev-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.gke ? 1 : 0
+ project_id = var.automation.project_id
+ name = "gke-dev-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = concat(
+ (
+ local.groups.gcp-devops == null
+ ? []
+ : ["group:${local.groups.gcp-devops}"]
+ ),
+ compact([
+ try(module.branch-gke-dev-sa-cicd.0.iam_email, null)
+ ])
+ )
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-gke-prod-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.gke ? 1 : 0
+ project_id = var.automation.project_id
+ name = "gke-prod-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = concat(
+ (
+ local.groups.gcp-devops == null
+ ? []
+ : ["group:${local.groups.gcp-devops}"]
+ ),
+ compact([
+ try(module.branch-gke-prod-sa-cicd.0.iam_email, null)
+ ])
+ )
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-gke-dev-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.gke ? 1 : 0
+ project_id = var.automation.project_id
+ name = "dev-resman-gke-0"
+ prefix = var.prefix
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.gke-dev]
+ }
+}
+
+module "branch-gke-prod-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.gke ? 1 : 0
+ project_id = var.automation.project_id
+ name = "prod-resman-gke-0"
+ prefix = var.prefix
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.gke-prod]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-networking.tf b/fast/stages-multitenant/1-resman-tenant/branch-networking.tf
new file mode 100644
index 0000000000..85490baf03
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-networking.tf
@@ -0,0 +1,107 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Networking stage resources.
+
+module "branch-network-folder" {
+ source = "../../../modules/folder"
+ parent = module.root-folder.id
+ name = "Networking"
+ group_iam = local.groups.gcp-network-admins == null ? {} : {
+ (local.groups.gcp-network-admins) = [
+ # add any needed roles for resources/services not managed via Terraform,
+ # or replace editor with ~viewer if no broad resource management needed
+ # e.g.
+ # "roles/compute.networkAdmin",
+ # "roles/dns.admin",
+ # "roles/compute.securityAdmin",
+ "roles/editor",
+ ]
+ }
+ iam = {
+ "roles/logging.admin" = [local.automation_sas_iam.networking]
+ "roles/owner" = [local.automation_sas_iam.networking]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.networking]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.networking]
+ "roles/compute.xpnAdmin" = [local.automation_sas_iam.networking]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.context}/networking"]
+ }
+}
+
+module "branch-network-prod-folder" {
+ source = "../../../modules/folder"
+ parent = module.branch-network-folder.id
+ name = "Production"
+ iam = {
+ (local.custom_roles.service_project_network_admin) = concat(
+ local.branch_optional_sa_lists.dp-prod,
+ local.branch_optional_sa_lists.gke-prod,
+ local.branch_optional_sa_lists.pf-prod,
+ )
+ }
+ tag_bindings = {
+ environment = var.tags.values["${var.tags.names.environment}/production"]
+ }
+}
+
+module "branch-network-dev-folder" {
+ source = "../../../modules/folder"
+ parent = module.branch-network-folder.id
+ name = "Development"
+ iam = {
+ (local.custom_roles.service_project_network_admin) = concat(
+ local.branch_optional_sa_lists.dp-dev,
+ local.branch_optional_sa_lists.gke-dev,
+ local.branch_optional_sa_lists.pf-dev,
+ )
+ }
+ tag_bindings = {
+ environment = var.tags.values["${var.tags.names.environment}/development"]
+ }
+}
+
+# automation service account and bucket
+
+module "branch-network-sa" {
+ source = "../../../modules/iam-service-account"
+ project_id = var.automation.project_id
+ name = "networking-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.branch-network-sa-cicd.0.iam_email, null)
+ ])
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-network-gcs" {
+ source = "../../../modules/gcs"
+ project_id = var.automation.project_id
+ name = "prod-resman-net-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.networking]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-project-factory.tf b/fast/stages-multitenant/1-resman-tenant/branch-project-factory.tf
new file mode 100644
index 0000000000..2fa64bbc52
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-project-factory.tf
@@ -0,0 +1,79 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Project factory stage resources.
+
+module "branch-pf-dev-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.project_factory ? 1 : 0
+ project_id = var.automation.project_id
+ name = "pf-dev-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.branch-pf-dev-sa-cicd.0.iam_email, null)
+ ])
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-pf-prod-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.project_factory ? 1 : 0
+ project_id = var.automation.project_id
+ name = "pf-prod-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.branch-pf-prod-sa-cicd.0.iam_email, null)
+ ])
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-pf-dev-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.project_factory ? 1 : 0
+ project_id = var.automation.project_id
+ name = "dev-resman-pf-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.pf-dev]
+ }
+}
+
+module "branch-pf-prod-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.project_factory ? 1 : 0
+ project_id = var.automation.project_id
+ name = "prod-resman-pf-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.pf-prod]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-sandbox.tf b/fast/stages-multitenant/1-resman-tenant/branch-sandbox.tf
new file mode 100644
index 0000000000..6f3d526c85
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-sandbox.tf
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Sandbox stage resources.
+
+module "branch-sandbox-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.sandbox ? 1 : 0
+ parent = module.root-folder.id
+ name = "Sandbox"
+ iam = {
+ "roles/logging.admin" = [local.automation_sas_iam.sandbox]
+ "roles/owner" = [local.automation_sas_iam.sandbox]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.sandbox]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.sandbox]
+ }
+ org_policies = {
+ "constraints/sql.restrictPublicIp" = { enforce = false }
+ "constraints/compute.vmExternalIpAccess" = { allow = { all = true } }
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.context}/sandbox"]
+ }
+}
+
+module "branch-sandbox-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.sandbox ? 1 : 0
+ project_id = var.automation.project_id
+ name = "dev-resman-sbox-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.sandbox]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-security.tf b/fast/stages-multitenant/1-resman-tenant/branch-security.tf
new file mode 100644
index 0000000000..d7253cce1a
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-security.tf
@@ -0,0 +1,76 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Security stage resources.
+
+module "branch-security-folder" {
+ source = "../../../modules/folder"
+ parent = module.root-folder.id
+ name = "Security"
+ group_iam = local.groups.gcp-security-admins == null ? {} : {
+ (local.groups.gcp-security-admins) = [
+ # add any needed roles for resources/services not managed via Terraform,
+ # e.g.
+ # "roles/bigquery.admin",
+ # "roles/cloudasset.owner",
+ # "roles/cloudkms.admin",
+ # "roles/logging.admin",
+ # "roles/secretmanager.admin",
+ # "roles/storage.admin",
+ "roles/viewer"
+ ]
+ }
+ iam = {
+ "roles/logging.admin" = [local.automation_sas_iam.security]
+ "roles/owner" = [local.automation_sas_iam.security]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.security]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.security]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.context}/security"]
+ }
+}
+
+# automation service account and bucket
+
+module "branch-security-sa" {
+ source = "../../../modules/iam-service-account"
+ project_id = var.automation.project_id
+ name = "security-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = compact([
+ try(module.branch-security-sa-cicd.0.iam_email, null)
+ ])
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-security-gcs" {
+ source = "../../../modules/gcs"
+ project_id = var.automation.project_id
+ name = "prod-resman-sec-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [local.automation_sas_iam.security]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/branch-teams.tf b/fast/stages-multitenant/1-resman-tenant/branch-teams.tf
new file mode 100644
index 0000000000..57f221104f
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/branch-teams.tf
@@ -0,0 +1,163 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Team stage resources.
+
+# TODO(ludo): add support for CI/CD
+
+############### top-level Teams branch and automation resources ###############
+
+module "branch-teams-folder" {
+ source = "../../../modules/folder"
+ count = var.fast_features.teams ? 1 : 0
+ parent = module.root-folder.id
+ name = "Teams"
+ iam = {
+ "roles/logging.admin" = [local.automation_sas_iam.teams]
+ "roles/owner" = [local.automation_sas_iam.teams]
+ "roles/resourcemanager.folderAdmin" = [local.automation_sas_iam.teams]
+ "roles/resourcemanager.projectCreator" = [local.automation_sas_iam.teams]
+ "roles/compute.xpnAdmin" = [local.automation_sas_iam.teams]
+ }
+ tag_bindings = {
+ context = var.tags.values["${var.tags.names.context}/teams"]
+ }
+}
+
+module "branch-teams-sa" {
+ source = "../../../modules/iam-service-account"
+ count = var.fast_features.teams ? 1 : 0
+ project_id = var.automation.project_id
+ name = "teams-0"
+ prefix = var.prefix
+ service_account_create = var.test_skip_data_sources
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.admin"]
+ }
+}
+
+module "branch-teams-gcs" {
+ source = "../../../modules/gcs"
+ count = var.fast_features.teams ? 1 : 0
+ project_id = var.automation.project_id
+ name = "prod-resman-teams-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [module.branch-teams-sa.0.iam_email]
+ }
+}
+
+################## per-team folders and automation resources ##################
+
+module "branch-teams-team-folder" {
+ source = "../../../modules/folder"
+ for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {}
+ parent = module.branch-teams-folder.0.id
+ name = each.value.descriptive_name
+ iam = {
+ "roles/logging.admin" = [module.branch-teams-team-sa[each.key].iam_email]
+ "roles/owner" = [module.branch-teams-team-sa[each.key].iam_email]
+ "roles/resourcemanager.folderAdmin" = [module.branch-teams-team-sa[each.key].iam_email]
+ "roles/resourcemanager.projectCreator" = [module.branch-teams-team-sa[each.key].iam_email]
+ "roles/compute.xpnAdmin" = [module.branch-teams-team-sa[each.key].iam_email]
+ }
+ group_iam = each.value.group_iam == null ? {} : each.value.group_iam
+}
+
+module "branch-teams-team-sa" {
+ source = "../../../modules/iam-service-account"
+ for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {}
+ project_id = var.automation.project_id
+ name = "prod-teams-${each.key}-0"
+ display_name = "Terraform team ${each.key} service account."
+ prefix = var.prefix
+ iam = {
+ "roles/iam.serviceAccountTokenCreator" = (
+ each.value.impersonation_groups == null
+ ? []
+ : [for g in each.value.impersonation_groups : "group:${g}"]
+ )
+ }
+}
+
+module "branch-teams-team-gcs" {
+ source = "../../../modules/gcs"
+ for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {}
+ project_id = var.automation.project_id
+ name = "prod-teams-${each.key}-0"
+ prefix = var.prefix
+ location = var.locations.gcs
+ storage_class = local.gcs_storage_class
+ versioning = true
+ iam = {
+ "roles/storage.objectAdmin" = [module.branch-teams-team-sa[each.key].iam_email]
+ }
+}
+
+# per-team environment folders where project factory SAs can create projects
+
+module "branch-teams-team-dev-folder" {
+ source = "../../../modules/folder"
+ for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {}
+ parent = module.branch-teams-team-folder[each.key].id
+ # naming: environment descriptive name
+ name = "Development"
+ # environment-wide human permissions on the whole teams environment
+ group_iam = {}
+ iam = {
+ (local.custom_roles.service_project_network_admin) = (
+ local.branch_optional_sa_lists.pf-dev
+ )
+ # remove owner here and at project level if SA does not manage project resources
+ "roles/owner" = local.branch_optional_sa_lists.pf-dev
+ "roles/logging.admin" = local.branch_optional_sa_lists.pf-dev
+ "roles/resourcemanager.folderAdmin" = local.branch_optional_sa_lists.pf-dev
+ "roles/resourcemanager.projectCreator" = local.branch_optional_sa_lists.pf-dev
+ }
+ tag_bindings = {
+ environment = try(
+ var.tags.values["${var.tags.names.environment}/development"], null
+ )
+ }
+}
+
+module "branch-teams-team-prod-folder" {
+ source = "../../../modules/folder"
+ for_each = var.fast_features.teams ? coalesce(var.team_folders, {}) : {}
+ parent = module.branch-teams-team-folder[each.key].id
+ # naming: environment descriptive name
+ name = "Production"
+ # environment-wide human permissions on the whole teams environment
+ group_iam = {}
+ iam = {
+ (local.custom_roles.service_project_network_admin) = (
+ local.branch_optional_sa_lists.pf-prod
+ )
+ # remove owner here and at project level if SA does not manage project resources
+ "roles/owner" = local.branch_optional_sa_lists.pf-prod
+ "roles/logging.admin" = local.branch_optional_sa_lists.pf-prod
+ "roles/resourcemanager.folderAdmin" = local.branch_optional_sa_lists.pf-prod
+ "roles/resourcemanager.projectCreator" = local.branch_optional_sa_lists.pf-prod
+ }
+ tag_bindings = {
+ environment = try(
+ var.tags.values["${var.tags.names.environment}/production"], null
+ )
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-data-platform.tf b/fast/stages-multitenant/1-resman-tenant/cicd-data-platform.tf
new file mode 100644
index 0000000000..704f45d78b
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/cicd-data-platform.tf
@@ -0,0 +1,173 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description CI/CD resources for the data platform branch.
+
+# source repositories
+
+module "branch-dp-dev-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.data_platform_dev.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.data_platform_dev }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = local.branch_optional_sa_lists.dp-dev
+ "roles/source.reader" = compact([
+ try(module.branch-dp-dev-sa-cicd.0.iam_email, "")
+ ])
+ }
+ triggers = {
+ fast-03-dp-dev = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = [
+ "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
+ ]
+ service_account = module.branch-dp-dev-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-dp-dev-sa-cicd]
+}
+
+module "branch-dp-prod-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.data_platform_prod.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.data_platform_prod }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = local.branch_optional_sa_lists.dp-prod
+ "roles/source.reader" = [module.branch-dp-prod-sa-cicd.0.iam_email]
+ }
+ triggers = {
+ fast-03-dp-prod = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = [
+ "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
+ ]
+ service_account = module.branch-dp-prod-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-dp-prod-sa-cicd]
+}
+
+# SAs used by CI/CD workflows to impersonate automation SAs
+
+module "branch-dp-dev-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.data_platform_dev.name, null) != null
+ ? { 0 = local.cicd_repositories.data_platform_dev }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "dev-resman-dp-1"
+ display_name = "Terraform CI/CD data platform development service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
+
+module "branch-dp-prod-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.data_platform_prod.name, null) != null
+ ? { 0 = local.cicd_repositories.data_platform_prod }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "prod-resman-dp-1"
+ display_name = "Terraform CI/CD data platform production service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-gke.tf b/fast/stages-multitenant/1-resman-tenant/cicd-gke.tf
new file mode 100644
index 0000000000..dfd035a51b
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/cicd-gke.tf
@@ -0,0 +1,175 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description CI/CD resources for the data platform branch.
+
+# source repositories
+
+module "branch-gke-dev-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.gke_dev.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.gke_dev }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = compact([
+ try(module.branch-gke-dev-sa.0.iam_email, "")
+ ])
+ "roles/source.reader" = compact([
+ try(module.branch-gke-dev-sa-cicd.0.iam_email, "")
+ ])
+ }
+ triggers = {
+ fast-03-gke-dev = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = [
+ "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
+ ]
+ service_account = module.branch-gke-dev-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-gke-dev-sa-cicd]
+}
+
+module "branch-gke-prod-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.gke_prod.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.gke_prod }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = [module.branch-gke-prod-sa.0.iam_email]
+ "roles/source.reader" = [module.branch-gke-prod-sa-cicd.0.iam_email]
+ }
+ triggers = {
+ fast-03-gke-prod = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = [
+ "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
+ ]
+ service_account = module.branch-gke-prod-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-gke-prod-sa-cicd]
+}
+
+# SAs used by CI/CD workflows to impersonate automation SAs
+
+module "branch-gke-dev-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.gke_dev.name, null) != null
+ ? { 0 = local.cicd_repositories.gke_dev }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "dev-resman-gke-1"
+ display_name = "Terraform CI/CD GKE development service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
+
+module "branch-gke-prod-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.gke_prod.name, null) != null
+ ? { 0 = local.cicd_repositories.gke_prod }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "prod-resman-gke-1"
+ display_name = "Terraform CI/CD GKE production service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-networking.tf b/fast/stages-multitenant/1-resman-tenant/cicd-networking.tf
new file mode 100644
index 0000000000..dbaf587d63
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/cicd-networking.tf
@@ -0,0 +1,94 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description CI/CD resources for the networking branch.
+
+# source repository
+
+module "branch-network-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.networking.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.networking }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = [module.branch-network-sa.iam_email]
+ "roles/source.reader" = [module.branch-network-sa-cicd.0.iam_email]
+ }
+ triggers = {
+ fast-02-networking = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
+ service_account = module.branch-network-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-network-sa-cicd]
+}
+
+# SA used by CI/CD workflows to impersonate automation SAs
+
+module "branch-network-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.networking.name, null) != null
+ ? { 0 = local.cicd_repositories.networking }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "prod-resman-net-1"
+ display_name = "Terraform CI/CD stage 2 networking service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-project-factory.tf b/fast/stages-multitenant/1-resman-tenant/cicd-project-factory.tf
new file mode 100644
index 0000000000..4c46d8585e
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/cicd-project-factory.tf
@@ -0,0 +1,191 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description CI/CD resources for the teams branch.
+
+# source repositories
+
+moved {
+ from = module.branch-teams-dev-pf-cicd-repo
+ to = module.branch-pf-dev-cicd-repo
+}
+
+module "branch-pf-dev-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.project_factory_dev.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.project_factory_dev }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = local.branch_optional_sa_lists.pf-dev
+ "roles/source.reader" = [module.branch-pf-dev-sa-cicd.0.iam_email]
+ }
+ triggers = {
+ fast-03-pf-dev = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = [
+ "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
+ ]
+ service_account = module.branch-pf-dev-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-pf-dev-sa-cicd]
+}
+
+moved {
+ from = module.branch-teams-prod-pf-cicd-repo
+ to = module.branch-pf-prod-cicd-repo
+}
+
+module "branch-pf-prod-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.project_factory_prod.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.project_factory_prod }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = local.branch_optional_sa_lists.pf-prod
+ "roles/source.reader" = [module.branch-pf-prod-sa-cicd.0.iam_email]
+ }
+ triggers = {
+ fast-03-pf-prod = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = [
+ "**/*json", "**/*tf", "**/*yaml", ".cloudbuild/workflow.yaml"
+ ]
+ service_account = module.branch-pf-prod-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-pf-prod-sa-cicd]
+}
+
+# SAs used by CI/CD workflows to impersonate automation SAs
+
+moved {
+ from = module.branch-teams-dev-pf-sa-cicd
+ to = module.branch-pf-dev-sa-cicd
+}
+
+module "branch-pf-dev-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.project_factory_dev.name, null) != null
+ ? { 0 = local.cicd_repositories.project_factory_dev }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "dev-pf-resman-pf-1"
+ display_name = "Terraform CI/CD project factory development service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
+
+moved {
+ from = module.branch-teams-prod-pf-sa-cicd
+ to = module.branch-pf-prod-sa-cicd
+}
+
+module "branch-pf-prod-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.project_factory_prod.name, null) != null
+ ? { 0 = local.cicd_repositories.project_factory_prod }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "prod-pf-resman-pf-1"
+ display_name = "Terraform CI/CD project factory production service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ var.automation.federated_identity_pool,
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ var.automation.federated_identity_pool,
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/cicd-security.tf b/fast/stages-multitenant/1-resman-tenant/cicd-security.tf
new file mode 100644
index 0000000000..5cb1581cfa
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/cicd-security.tf
@@ -0,0 +1,94 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description CI/CD resources for the security branch.
+
+# source repository
+
+module "branch-security-cicd-repo" {
+ source = "../../../modules/source-repository"
+ for_each = (
+ try(local.cicd_repositories.security.type, null) == "sourcerepo"
+ ? { 0 = local.cicd_repositories.security }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = each.value.name
+ iam = {
+ "roles/source.admin" = [module.branch-security-sa.iam_email]
+ "roles/source.reader" = [module.branch-security-sa-cicd.0.iam_email]
+ }
+ triggers = {
+ fast-02-security = {
+ filename = ".cloudbuild/workflow.yaml"
+ included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
+ service_account = module.branch-security-sa-cicd.0.id
+ substitutions = {}
+ template = {
+ project_id = null
+ branch_name = each.value.branch
+ repo_name = each.value.name
+ tag_name = null
+ }
+ }
+ }
+ depends_on = [module.branch-security-sa-cicd]
+}
+
+# SA used by CI/CD workflows to impersonate automation SAs
+
+module "branch-security-sa-cicd" {
+ source = "../../../modules/iam-service-account"
+ for_each = (
+ try(local.cicd_repositories.security.name, null) != null
+ ? { 0 = local.cicd_repositories.security }
+ : {}
+ )
+ project_id = var.automation.project_id
+ name = "prod-resman-sec-1"
+ display_name = "Terraform CI/CD stage 2 security service account."
+ prefix = var.prefix
+ iam = (
+ each.value.type == "sourcerepo"
+ # used directly from the cloud build trigger for source repos
+ ? {
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
+ }
+ # impersonated via workload identity federation for external repos
+ : {
+ "roles/iam.workloadIdentityUser" = [
+ each.value.branch == null
+ ? format(
+ local.cicd_identity_providers[each.value.identity_provider].principalset_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name
+ )
+ : format(
+ local.cicd_identity_providers[each.value.identity_provider].principal_tpl,
+ local.cicd_identity_pools[each.value.identity_provider],
+ each.value.name,
+ each.value.branch
+ )
+ ]
+ }
+ )
+ iam_project_roles = {
+ (var.automation.project_id) = ["roles/logging.logWriter"]
+ }
+ iam_storage_roles = {
+ (var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
+ }
+}
diff --git a/fast/stages/01-resman/data/org-policies/compute.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/compute.yaml
similarity index 100%
rename from fast/stages/01-resman/data/org-policies/compute.yaml
rename to fast/stages-multitenant/1-resman-tenant/data/org-policies/compute.yaml
diff --git a/fast/stages/01-resman/data/org-policies/iam.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/iam.yaml
similarity index 100%
rename from fast/stages/01-resman/data/org-policies/iam.yaml
rename to fast/stages-multitenant/1-resman-tenant/data/org-policies/iam.yaml
diff --git a/fast/stages/01-resman/data/org-policies/serverless.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/serverless.yaml
similarity index 100%
rename from fast/stages/01-resman/data/org-policies/serverless.yaml
rename to fast/stages-multitenant/1-resman-tenant/data/org-policies/serverless.yaml
diff --git a/fast/stages/01-resman/data/org-policies/sql.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/sql.yaml
similarity index 100%
rename from fast/stages/01-resman/data/org-policies/sql.yaml
rename to fast/stages-multitenant/1-resman-tenant/data/org-policies/sql.yaml
diff --git a/fast/stages/01-resman/data/org-policies/storage.yaml b/fast/stages-multitenant/1-resman-tenant/data/org-policies/storage.yaml
similarity index 100%
rename from fast/stages/01-resman/data/org-policies/storage.yaml
rename to fast/stages-multitenant/1-resman-tenant/data/org-policies/storage.yaml
diff --git a/fast/stages/01-resman/diagram.png b/fast/stages-multitenant/1-resman-tenant/diagram.png
similarity index 100%
rename from fast/stages/01-resman/diagram.png
rename to fast/stages-multitenant/1-resman-tenant/diagram.png
diff --git a/fast/stages/01-resman/diagram.svg b/fast/stages-multitenant/1-resman-tenant/diagram.svg
similarity index 100%
rename from fast/stages/01-resman/diagram.svg
rename to fast/stages-multitenant/1-resman-tenant/diagram.svg
diff --git a/fast/stages-multitenant/1-resman-tenant/main.tf b/fast/stages-multitenant/1-resman-tenant/main.tf
new file mode 100644
index 0000000000..76c046396b
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/main.tf
@@ -0,0 +1,79 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+locals {
+ automation_resman_sa_iam = [
+ "serviceAccount:${var.automation.service_accounts.resman}"
+ ]
+ automation_sas_iam = {
+ for k, v in var.automation.service_accounts :
+ k => v == null ? null : "serviceAccount:${v}"
+ }
+ branch_optional_sa_lists = {
+ dp-dev = compact([local.automation_sas_iam.dp-dev])
+ dp-prod = compact([local.automation_sas_iam.dp-prod])
+ gke-dev = compact([local.automation_sas_iam.gke-dev])
+ gke-prod = compact([local.automation_sas_iam.gke-prod])
+ pf-dev = compact([local.automation_sas_iam.pf-dev])
+ pf-prod = compact([local.automation_sas_iam.pf-prod])
+ }
+ # derive identity pool names from identity providers for easy reference
+ cicd_identity_pools = {
+ for k, v in local.cicd_identity_providers :
+ k => split("/providers/", v.name)[0]
+ }
+ cicd_identity_providers = coalesce(
+ try(var.automation.federated_identity_providers, null), {}
+ )
+ cicd_repositories = {
+ for k, v in coalesce(var.cicd_repositories, {}) : k => v
+ if(
+ v != null &&
+ (
+ try(v.type, null) == "sourcerepo"
+ ||
+ contains(
+ keys(local.cicd_identity_providers),
+ coalesce(try(v.identity_provider, null), ":")
+ )
+ ) &&
+ fileexists("${path.module}/templates/workflow-${try(v.type, "")}.yaml")
+ )
+ }
+ cicd_workflow_var_files = {
+ stage_2 = [
+ "0-bootstrap-tenant.auto.tfvars.json",
+ ]
+ stage_3 = [
+ "0-bootstrap-tenant.auto.tfvars.json",
+ "2-networking.auto.tfvars.json",
+ "2-security.auto.tfvars.json"
+ ]
+ }
+ custom_roles = coalesce(var.custom_roles, {})
+ gcs_storage_class = (
+ length(split("-", var.locations.gcs)) < 2
+ ? "MULTI_REGIONAL"
+ : "REGIONAL"
+ )
+ groups = {
+ for k, v in var.groups :
+ k => v == null ? null : "${v}@${var.organization.domain}"
+ }
+ groups_iam = {
+ for k, v in local.groups : k => v != null ? "group:${v}" : null
+ }
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/outputs-files.tf b/fast/stages-multitenant/1-resman-tenant/outputs-files.tf
new file mode 100644
index 0000000000..29d5ed4606
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/outputs-files.tf
@@ -0,0 +1,46 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Output files persistence to local filesystem.
+
+locals {
+ outputs_root = join("/", [
+ try(pathexpand(var.outputs_location), ""),
+ "tenants",
+ var.short_name
+ ])
+}
+
+resource "local_file" "providers" {
+ for_each = var.outputs_location == null ? {} : local.providers
+ file_permission = "0644"
+ filename = "${local.outputs_root}/providers/${each.key}-providers.tf"
+ content = try(each.value, null)
+}
+
+resource "local_file" "tfvars" {
+ count = var.outputs_location == null ? 0 : 1
+ file_permission = "0644"
+ filename = "${local.outputs_root}/tfvars/1-resman.auto.tfvars.json"
+ content = jsonencode(local.tfvars)
+}
+
+resource "local_file" "workflows" {
+ for_each = var.outputs_location == null ? {} : local.cicd_workflows
+ file_permission = "0644"
+ filename = "${local.outputs_root}/workflows/${replace(each.key, "_", "-")}-workflow.yaml"
+ content = try(each.value, null)
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/outputs-gcs.tf b/fast/stages-multitenant/1-resman-tenant/outputs-gcs.tf
new file mode 100644
index 0000000000..6b0fc89cb0
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/outputs-gcs.tf
@@ -0,0 +1,37 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Output files persistence to automation GCS bucket.
+
+resource "google_storage_bucket_object" "providers" {
+ for_each = local.providers
+ bucket = var.automation.outputs_bucket
+ name = "providers/${each.key}-providers.tf"
+ content = each.value
+}
+
+resource "google_storage_bucket_object" "tfvars" {
+ bucket = var.automation.outputs_bucket
+ name = "tfvars/1-resman.auto.tfvars.json"
+ content = jsonencode(local.tfvars)
+}
+
+resource "google_storage_bucket_object" "workflows" {
+ for_each = local.cicd_workflows
+ bucket = var.automation.outputs_bucket
+ name = "workflows/${replace(each.key, "_", "-")}-workflow.yaml"
+ content = each.value
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/outputs.tf b/fast/stages-multitenant/1-resman-tenant/outputs.tf
new file mode 100644
index 0000000000..ad2d8dcfd3
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/outputs.tf
@@ -0,0 +1,311 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+locals {
+ _tpl_providers = "${path.module}/templates/providers.tf.tpl"
+ cicd_workflow_attrs = {
+ data_platform_dev = {
+ service_account = try(module.branch-dp-dev-sa-cicd.0.email, null)
+ tf_providers_file = "3-data-platform-dev-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_3
+ }
+ data_platform_prod = {
+ service_account = try(module.branch-dp-prod-sa-cicd.0.email, null)
+ tf_providers_file = "3-data-platform-prod-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_3
+ }
+ gke_dev = {
+ service_account = try(module.branch-gke-dev-sa-cicd.0.email, null)
+ tf_providers_file = "3-gke-dev-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_3
+ }
+ gke_prod = {
+ service_account = try(module.branch-gke-prod-sa-cicd.0.email, null)
+ tf_providers_file = "3-gke-prod-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_3
+ }
+ networking = {
+ service_account = try(module.branch-network-sa-cicd.0.email, null)
+ tf_providers_file = "2-networking-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_2
+ }
+ project_factory_dev = {
+ service_account = try(module.branch-pf-dev-sa-cicd.0.email, null)
+ tf_providers_file = "3-project-factory-dev-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_3
+ }
+ project_factory_prod = {
+ service_account = try(module.branch-pf-prod-sa-cicd.0.email, null)
+ tf_providers_file = "3-project-factory-prod-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_3
+ }
+ security = {
+ service_account = try(module.branch-security-sa-cicd.0.email, null)
+ tf_providers_file = "2-security-providers.tf"
+ tf_var_files = local.cicd_workflow_var_files.stage_2
+ }
+ }
+ cicd_workflows = {
+ for k, v in local.cicd_repositories : k => templatefile(
+ "${path.module}/templates/workflow-${v.type}.yaml",
+ merge(local.cicd_workflow_attrs[k], {
+ identity_provider = try(
+ local.cicd_identity_providers[v.identity_provider].name, null
+ )
+ outputs_bucket = var.automation.outputs_bucket
+ stage_name = k
+ })
+ )
+ }
+ folder_ids = merge(
+ {
+ data-platform-dev = try(module.branch-dp-dev-folder.0.id, null)
+ data-platform-prod = try(module.branch-dp-prod-folder.0.id, null)
+ gke-dev = try(module.branch-gke-dev-folder.0.id, null)
+ gke-prod = try(module.branch-gke-prod-folder.0.id, null)
+ networking = module.branch-network-folder.id
+ networking-dev = module.branch-network-dev-folder.id
+ networking-prod = module.branch-network-prod-folder.id
+ sandbox = try(module.branch-sandbox-folder.0.id, null)
+ security = module.branch-security-folder.id
+ teams = try(module.branch-teams-folder.0.id, null)
+ },
+ {
+ for k, v in module.branch-teams-team-folder :
+ "team-${k}" => v.id
+ },
+ {
+ for k, v in module.branch-teams-team-dev-folder :
+ "team-${k}-dev" => v.id
+ },
+ {
+ for k, v in module.branch-teams-team-prod-folder :
+ "team-${k}-prod" => v.id
+ }
+ )
+ providers = merge(
+ {
+ "2-0-networking" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-network-gcs.name
+ name = "networking"
+ sa = module.branch-network-sa.email
+ })
+ "2-0-security" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-security-gcs.name
+ name = "security"
+ sa = module.branch-security-sa.email
+ })
+ },
+ !var.fast_features.data_platform ? {} : {
+ "3-0-data-platform-dev" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-dp-dev-gcs.0.name
+ name = "dp-dev"
+ sa = module.branch-dp-dev-sa.0.email
+ })
+ "3-0-data-platform-prod" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-dp-prod-gcs.0.name
+ name = "dp-prod"
+ sa = module.branch-dp-prod-sa.0.email
+ })
+ },
+ !var.fast_features.gke ? {} : {
+ "3-0-gke-dev" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-gke-dev-gcs.0.name
+ name = "gke-dev"
+ sa = module.branch-gke-dev-sa.0.email
+ })
+ "3-0-gke-prod" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-gke-prod-gcs.0.name
+ name = "gke-prod"
+ sa = module.branch-gke-prod-sa.0.email
+ })
+ },
+ !var.fast_features.project_factory ? {} : {
+ "3-0-project-factory-dev" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-pf-dev-gcs.0.name
+ name = "team-dev"
+ sa = var.automation.service_accounts.pf-dev
+ })
+ "3-0-project-factory-prod" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-pf-prod-gcs.0.name
+ name = "team-prod"
+ sa = var.automation.service_accounts.pf-prod
+ })
+ },
+ !var.fast_features.sandbox ? {} : {
+ "9-0-sandbox" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-sandbox-gcs.0.name
+ name = "sandbox"
+ sa = var.automation.service_accounts.sandbox
+ })
+ },
+ !var.fast_features.teams ? {} : merge(
+ {
+ "3-teams" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-teams-gcs.0.name
+ name = "teams"
+ sa = module.branch-teams-sa.0.email
+ })
+ },
+ {
+ for k, v in module.branch-teams-team-sa :
+ "3-teams-${k}" => templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.branch-teams-team-gcs[k].name
+ name = "teams"
+ sa = v.email
+ })
+ }
+ )
+ )
+ tfvars = {
+ folder_ids = local.folder_ids
+ }
+}
+
+output "cicd_repositories" {
+ description = "WIF configuration for CI/CD repositories."
+ value = {
+ for k, v in local.cicd_repositories : k => {
+ branch = v.branch
+ name = v.name
+ provider = try(
+ local.cicd_identity_providers[v.identity_provider].name, null
+ )
+ service_account = local.cicd_workflow_attrs[k].service_account
+ } if v != null
+ }
+}
+
+output "dataplatform" {
+ description = "Data for the Data Platform stage."
+ value = !var.fast_features.data_platform ? {} : {
+ dev = {
+ folder = module.branch-dp-dev-folder.0.id
+ gcs_bucket = module.branch-dp-dev-gcs.0.name
+ service_account = module.branch-dp-dev-sa.0.email
+ }
+ prod = {
+ folder = module.branch-dp-prod-folder.0.id
+ gcs_bucket = module.branch-dp-prod-gcs.0.name
+ service_account = module.branch-dp-prod-sa.0.email
+ }
+ }
+}
+
+output "gke_multitenant" {
+ # tfdoc:output:consumers 03-gke-multitenant
+ description = "Data for the GKE multitenant stage."
+ value = (
+ var.fast_features.gke
+ ? {
+ "dev" = {
+ folder = module.branch-gke-dev-folder.0.id
+ gcs_bucket = module.branch-gke-dev-gcs.0.name
+ service_account = module.branch-gke-dev-sa.0.email
+ }
+ "prod" = {
+ folder = module.branch-gke-prod-folder.0.id
+ gcs_bucket = module.branch-gke-prod-gcs.0.name
+ service_account = module.branch-gke-prod-sa.0.email
+ }
+ }
+ : {}
+ )
+}
+
+output "networking" {
+ description = "Data for the networking stage."
+ value = {
+ folder = module.branch-network-folder.id
+ gcs_bucket = module.branch-network-gcs.name
+ service_account = module.branch-network-sa.iam_email
+ }
+}
+
+output "project_factories" {
+ description = "Data for the project factories stage."
+ value = !var.fast_features.project_factory ? {} : {
+ dev = {
+ bucket = module.branch-pf-dev-gcs.0.name
+ sa = var.automation.service_accounts.pf-dev
+ }
+ prod = {
+ bucket = module.branch-pf-prod-gcs.0.name
+ sa = var.automation.service_accounts.pf-prod
+ }
+ }
+}
+
+# ready to use provider configurations for subsequent stages
+output "providers" {
+ # tfdoc:output:consumers 02-networking 02-security 03-dataplatform xx-sandbox xx-teams
+ description = "Terraform provider files for this stage and dependent stages."
+ sensitive = true
+ value = local.providers
+}
+
+output "sandbox" {
+ # tfdoc:output:consumers xx-sandbox
+ description = "Data for the sandbox stage."
+ value = (
+ var.fast_features.sandbox
+ ? {
+ folder = module.branch-sandbox-folder.0.id
+ gcs_bucket = module.branch-sandbox-gcs.0.name
+ service_account = var.automation.service_accounts.sandbox
+ }
+ : null
+ )
+}
+
+output "security" {
+ # tfdoc:output:consumers 02-security
+ description = "Data for the networking stage."
+ value = {
+ folder = module.branch-security-folder.id
+ gcs_bucket = module.branch-security-gcs.name
+ service_account = module.branch-security-sa.iam_email
+ }
+}
+
+output "teams" {
+ description = "Data for the teams stage."
+ value = {
+ for k, v in module.branch-teams-team-folder : k => {
+ folder = v.id
+ gcs_bucket = module.branch-teams-team-gcs[k].name
+ service_account = module.branch-teams-team-sa[k].email
+ }
+ }
+}
+
+# ready to use variable values for subsequent stages
+output "tfvars" {
+ description = "Terraform variable files for the following stages."
+ sensitive = true
+ value = local.tfvars
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/root_node.tf b/fast/stages-multitenant/1-resman-tenant/root_node.tf
new file mode 100644
index 0000000000..5b83d2dd22
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/root_node.tf
@@ -0,0 +1,41 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Tenant root folder configuration.
+
+module "root-folder" {
+ source = "../../../modules/folder"
+ id = var.root_node
+ folder_create = var.test_skip_data_sources
+ # start test attributes
+ parent = (
+ var.test_skip_data_sources ? "organizations/${var.organization.id}" : null
+ )
+ name = var.test_skip_data_sources ? "Test" : null
+ # end test attributes
+ iam_additive = {
+ "roles/accesscontextmanager.policyAdmin" = [
+ local.automation_sas_iam.security
+ ]
+ "roles/compute.orgFirewallPolicyAdmin" = [
+ local.automation_sas_iam.networking
+ ]
+ "roles/compute.xpnAdmin" = [
+ local.automation_sas_iam.networking
+ ]
+ }
+ org_policies_data_path = var.organization_policy_data_path
+}
diff --git a/fast/stages-multitenant/1-resman-tenant/templates/providers.tf.tpl b/fast/stages-multitenant/1-resman-tenant/templates/providers.tf.tpl
new file mode 100644
index 0000000000..993c78ca41
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/templates/providers.tf.tpl
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+terraform {
+ backend "gcs" {
+ bucket = "${bucket}"
+ impersonate_service_account = "${sa}"
+ %{~ if backend_extra != null ~}
+ ${indent(4, backend_extra)}
+ %{~ endif ~}
+ }
+}
+provider "google" {
+ impersonate_service_account = "${sa}"
+}
+provider "google-beta" {
+ impersonate_service_account = "${sa}"
+}
+
+# end provider.tf for ${name}
diff --git a/fast/stages-multitenant/1-resman-tenant/templates/workflow-github.yaml b/fast/stages-multitenant/1-resman-tenant/templates/workflow-github.yaml
new file mode 100644
index 0000000000..8a946d84c8
--- /dev/null
+++ b/fast/stages-multitenant/1-resman-tenant/templates/workflow-github.yaml
@@ -0,0 +1,186 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: "FAST ${stage_name} stage"
+
+on:
+ pull_request:
+ branches:
+ - main
+ types:
+ - closed
+ - opened
+ - synchronize
+
+env:
+ FAST_OUTPUTS_BUCKET: ${outputs_bucket}
+ FAST_SERVICE_ACCOUNT: ${service_account}
+ FAST_WIF_PROVIDER: ${identity_provider}
+ SSH_AUTH_SOCK: /tmp/ssh_agent.sock
+ TF_PROVIDERS_FILE: ${tf_providers_file}
+ TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)}
+ TF_VERSION: 1.3.2
+
+jobs:
+ fast-pr:
+ permissions:
+ contents: read
+ id-token: write
+ issues: write
+ pull-requests: write
+ runs-on: ubuntu-latest
+ steps:
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@v3
+
+ # set up SSH key authentication to the modules repository
+ - id: ssh-config
+ name: Configure SSH authentication
+ run: |
+ ssh-agent -a "$SSH_AUTH_SOCK" > /dev/null
+ ssh-add - <<< "$${{ secrets.CICD_MODULES_KEY }}"
+
+ # set up authentication via Workload identity Federation
+ - id: gcp-auth
+ name: Authenticate to Google Cloud
+ uses: google-github-actions/auth@v0
+ with:
+ workload_identity_provider: $${{ env.FAST_WIF_PROVIDER }}
+ service_account: $${{ env.FAST_SERVICE_ACCOUNT }}
+ access_token_lifetime: 3600s
+
+ - id: gcp-sdk
+ name: Set up Cloud SDK
+ uses: google-github-actions/setup-gcloud@v0
+ with:
+ install_components: alpha
+
+ # copy provider and tfvars files
+ - id: tf-config
+ name: Copy Terraform output files
+ run: |
+ gcloud alpha storage cp -r \
+ "gs://$${{env.FAST_OUTPUTS_BUCKET}}/providers/$${{env.TF_PROVIDERS_FILE}}" ./
+ gcloud alpha storage cp -r \
+ "gs://$${{env.FAST_OUTPUTS_BUCKET}}/tfvars" ./
+ for f in $${{env.TF_VAR_FILES}}; do
+ ln -s "tfvars/$f" ./
+ done
+
+ - id: tf-setup
+ name: Set up Terraform
+ uses: hashicorp/setup-terraform@v2.0.3
+ with:
+ terraform_version: $${{ env.TF_VERSION }}
+
+ # run Terraform init/validate/plan
+ - id: tf-init
+ name: Terraform init
+ run: |
+ terraform init -no-color
+
+ - id: tf-validate
+ name: Terraform validate
+ run: terraform validate -no-color
+
+ - id: tf-plan
+ name: Terraform plan
+ continue-on-error: true
+ run: |
+ terraform plan -input=false -out ../plan.out -no-color
+
+ - id: tf-apply
+ if: github.event.pull_request.merged == true && success()
+ name: Terraform apply
+ continue-on-error: true
+ run: |
+ terraform apply -input=false -auto-approve -no-color ../plan.out
+
+ - id: pr-comment
+ name: Post comment to Pull Request
+ continue-on-error: true
+ uses: actions/github-script@v6
+ if: github.event_name == 'pull_request'
+ env:
+ PLAN: $${{ steps.tf-plan.outputs.stdout }}\n$${{ steps.tf-plan.outputs.stderr }}
+ with:
+ script: |
+ const output = `### Terraform Initialization \`$${{ steps.tf-init.outcome }}\`
+
+ ### Terraform Validation \`$${{ steps.tf-validate.outcome }}\`
+
+ + +
+ +## Tenant bootstrap (0) + +This stage creates the top-level root folder and tag for the tenant, and the tenant-level automation project and automation service accounts. It also sets up billing and organization-level roles for the tenant administrators group and the automation service accounts. As in the organizational-level stages, it can optionally set up CI/CD for itself and the tenant resource management stage. + +This stage is run with the organization-level resource management service account as it leverages its permissions, and is the bridge between the organization-level stages and the tenant stages which are effectively decoupled from the rest of the organization. + +## Tenant resource management (1) + +This stage populates the resource hierarchy rooted in the top-level tenant folder, assigns roles to the tenant automation service accounts, and optionally sets up CI/CD for the following stages. It is functionally equivalent to the organization-level resource management stage, but runs with a tenant-specific service account and has no control over resources outside of the tenant context. diff --git a/fast/stages-multitenant/diagram.png b/fast/stages-multitenant/diagram.png new file mode 100644 index 0000000000..940fa3c36e Binary files /dev/null and b/fast/stages-multitenant/diagram.png differ diff --git a/fast/stages-multitenant/stages.png b/fast/stages-multitenant/stages.png new file mode 100644 index 0000000000..dc2acc0a28 Binary files /dev/null and b/fast/stages-multitenant/stages.png differ diff --git a/fast/stages-multitenant/stages.svg b/fast/stages-multitenant/stages.svg new file mode 100644 index 0000000000..157453f0dc --- /dev/null +++ b/fast/stages-multitenant/stages.svg @@ -0,0 +1,1278 @@ + +gcs
· iam-service-account
· project
| |
-| [billing.tf](./billing.tf) | Billing export project and dataset. | bigquery-dataset
· organization
· project
| google_billing_account_iam_member
· google_organization_iam_binding
|
+| [billing.tf](./billing.tf) | Billing export project and dataset. | bigquery-dataset
· project
| google_billing_account_iam_member
|
| [cicd.tf](./cicd.tf) | Workload Identity Federation configurations for CI/CD. | iam-service-account
· source-repository
| |
| [identity-providers.tf](./identity-providers.tf) | Workload Identity Federation provider definitions. | | google_iam_workload_identity_pool
· google_iam_workload_identity_pool_provider
|
| [log-export.tf](./log-export.tf) | Audit log project and sink. | bigquery-dataset
· gcs
· logging-bucket
· project
· pubsub
| |
@@ -468,35 +468,35 @@ The remaining configuration is manual, as it regards the repositories themselves
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [billing_account](variables.tf#L17) | Billing account id and organization id ('nnnnnnnn' or null). | object({…})
| ✓ | | |
-| [organization](variables.tf#L202) | Organization details. | object({…})
| ✓ | | |
-| [prefix](variables.tf#L217) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | |
-| [bootstrap_user](variables.tf#L25) | Email of the nominal user running this stage for the first time. | string
| | null
| |
-| [cicd_repositories](variables.tf#L31) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…})
| | null
| |
-| [custom_role_names](variables.tf#L83) | Names of custom roles defined at the org level. | object({…})
| | {…}
| |
-| [fast_features](variables.tf#L95) | Selective control for top-level FAST features. | object({…})
| | {…}
| |
-| [federated_identity_providers](variables.tf#L114) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…}))
| | {}
| |
-| [groups](variables.tf#L128) | Group names to grant organization-level permissions. | map(string)
| | {…}
| |
-| [iam](variables.tf#L146) | Organization-level custom IAM settings in role => [principal] format. | map(list(string))
| | {}
| |
-| [iam_additive](variables.tf#L152) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | map(list(string))
| | {}
| |
-| [locations](variables.tf#L158) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…})
| | {…}
| |
-| [log_sinks](variables.tf#L177) | Org-level log sinks, in name => {type, filter} format. | map(object({…}))
| | {…}
| |
-| [outputs_location](variables.tf#L211) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string
| | null
| |
-| [project_parent_ids](variables.tf#L227) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…})
| | {…}
| |
+| [billing_account](variables.tf#L17) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…})
| ✓ | | |
+| [organization](variables.tf#L194) | Organization details. | object({…})
| ✓ | | |
+| [prefix](variables.tf#L209) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | |
+| [bootstrap_user](variables.tf#L29) | Email of the nominal user running this stage for the first time. | string
| | null
| |
+| [cicd_repositories](variables.tf#L35) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…})
| | null
| |
+| [custom_role_names](variables.tf#L81) | Names of custom roles defined at the org level. | object({…})
| | {…}
| |
+| [fast_features](variables.tf#L93) | Selective control for top-level FAST features. | object({…})
| | {}
| |
+| [federated_identity_providers](variables.tf#L106) | Workload Identity Federation pools. The `cicd_repositories` variable references keys here. | map(object({…}))
| | {}
| |
+| [groups](variables.tf#L120) | Group names to grant organization-level permissions. | map(string)
| | {…}
| |
+| [iam](variables.tf#L138) | Organization-level custom IAM settings in role => [principal] format. | map(list(string))
| | {}
| |
+| [iam_additive](variables.tf#L144) | Organization-level custom IAM settings in role => [principal] format for non-authoritative bindings. | map(list(string))
| | {}
| |
+| [locations](variables.tf#L150) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…})
| | {…}
| |
+| [log_sinks](variables.tf#L169) | Org-level log sinks, in name => {type, filter} format. | map(object({…}))
| | {…}
| |
+| [outputs_location](variables.tf#L203) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string
| | null
| |
+| [project_parent_ids](variables.tf#L219) | Optional parents for projects created here in folders/nnnnnnn format. Null values will use the organization as parent. | object({…})
| | {…}
| |
## Outputs
| name | description | sensitive | consumers |
|---|---|:---:|---|
-| [automation](outputs.tf#L89) | Automation resources. | | |
-| [billing_dataset](outputs.tf#L94) | BigQuery dataset prepared for billing export. | | |
-| [cicd_repositories](outputs.tf#L99) | CI/CD repository configurations. | | |
-| [custom_roles](outputs.tf#L111) | Organization-level custom roles. | | |
-| [federated_identity](outputs.tf#L116) | Workload Identity Federation pool and providers. | | |
-| [outputs_bucket](outputs.tf#L126) | GCS bucket where generated output files are stored. | | |
-| [project_ids](outputs.tf#L131) | Projects created by this stage. | | |
-| [providers](outputs.tf#L141) | Terraform provider files for this stage and dependent stages. | ✓ | stage-01
|
-| [service_accounts](outputs.tf#L148) | Automation service accounts created by this stage. | | |
-| [tfvars](outputs.tf#L158) | Terraform variable files for the following stages. | ✓ | |
+| [automation](outputs.tf#L86) | Automation resources. | | |
+| [billing_dataset](outputs.tf#L91) | BigQuery dataset prepared for billing export. | | |
+| [cicd_repositories](outputs.tf#L96) | CI/CD repository configurations. | | |
+| [custom_roles](outputs.tf#L108) | Organization-level custom roles. | | |
+| [federated_identity](outputs.tf#L113) | Workload Identity Federation pool and providers. | | |
+| [outputs_bucket](outputs.tf#L123) | GCS bucket where generated output files are stored. | | |
+| [project_ids](outputs.tf#L128) | Projects created by this stage. | | |
+| [providers](outputs.tf#L138) | Terraform provider files for this stage and dependent stages. | ✓ | stage-01
|
+| [service_accounts](outputs.tf#L145) | Automation service accounts created by this stage. | | |
+| [tfvars](outputs.tf#L154) | Terraform variable files for the following stages. | ✓ | |
diff --git a/fast/stages/00-bootstrap/automation.tf b/fast/stages/0-bootstrap/automation.tf
similarity index 82%
rename from fast/stages/00-bootstrap/automation.tf
rename to fast/stages/0-bootstrap/automation.tf
index 1475c811c9..90c14a81d3 100644
--- a/fast/stages/00-bootstrap/automation.tf
+++ b/fast/stages/0-bootstrap/automation.tf
@@ -127,39 +127,6 @@ module "automation-tf-bootstrap-sa" {
}
}
-# cicd stage's bucket and service account
-
-module "automation-tf-cicd-gcs" {
- source = "../../../modules/gcs"
- project_id = module.automation-project.project_id
- name = "iac-core-cicd-0"
- prefix = local.prefix
- location = var.locations.gcs
- storage_class = local.gcs_storage_class
- versioning = true
- iam = {
- "roles/storage.objectAdmin" = [module.automation-tf-cicd-provisioning-sa.iam_email]
- }
- depends_on = [module.organization]
-}
-
-module "automation-tf-cicd-provisioning-sa" {
- source = "../../../modules/iam-service-account"
- project_id = module.automation-project.project_id
- name = "cicd-0"
- display_name = "Terraform stage 1 CICD service account."
- prefix = local.prefix
- # allow SA used by CI/CD workflow to impersonate this SA
- iam = {
- "roles/iam.serviceAccountTokenCreator" = compact([
- try(module.automation-tf-cicd-sa["cicd"].iam_email, null)
- ])
- }
- iam_storage_roles = {
- (module.automation-tf-output-gcs.name) = ["roles/storage.admin"]
- }
-}
-
# resource hierarchy stage's bucket and service account
module "automation-tf-resman-gcs" {
@@ -183,7 +150,8 @@ module "automation-tf-resman-sa" {
display_name = "Terraform stage 1 resman service account."
prefix = local.prefix
# allow SA used by CI/CD workflow to impersonate this SA
- iam = {
+ # we use additive IAM to allow tenant CI/CD SAs to impersonate it
+ iam_additive = {
"roles/iam.serviceAccountTokenCreator" = compact([
try(module.automation-tf-cicd-sa["resman"].iam_email, null)
])
diff --git a/fast/stages/00-bootstrap/billing.tf b/fast/stages/0-bootstrap/billing.tf
similarity index 60%
rename from fast/stages/00-bootstrap/billing.tf
rename to fast/stages/0-bootstrap/billing.tf
index df10e8f085..aee033bd8a 100644
--- a/fast/stages/00-bootstrap/billing.tf
+++ b/fast/stages/0-bootstrap/billing.tf
@@ -30,7 +30,7 @@ locals {
module "billing-export-project" {
source = "../../../modules/project"
- count = local.billing_org ? 1 : 0
+ count = var.billing_account.is_org_level ? 1 : 0
billing_account = var.billing_account.id
name = "billing-exp-0"
parent = coalesce(
@@ -52,56 +52,18 @@ module "billing-export-project" {
module "billing-export-dataset" {
source = "../../../modules/bigquery-dataset"
- count = local.billing_org ? 1 : 0
+ count = var.billing_account.is_org_level ? 1 : 0
project_id = module.billing-export-project.0.project_id
id = "billing_export"
friendly_name = "Billing export."
location = var.locations.bq
}
-# billing account in a different org
-
-module "billing-organization-ext" {
- source = "../../../modules/organization"
- count = local.billing_org_ext ? 1 : 0
- organization_id = "organizations/${var.billing_account.organization_id}"
- iam_additive = {
- "roles/billing.admin" = local.billing_ext_admins
- }
-}
-
-
-resource "google_organization_iam_binding" "billing_org_ext_admin_delegated" {
- # refer to organization.tf for the explanation of how this binding works
- count = local.billing_org_ext ? 1 : 0
- org_id = var.billing_account.organization_id
- # if the billing org does not have our custom role, user the predefined one
- # role = "roles/resourcemanager.organizationAdmin"
- role = join("", [
- "organizations/${var.billing_account.organization_id}/",
- "roles/${var.custom_role_names.organization_iam_admin}"
- ])
- members = [module.automation-tf-resman-sa.iam_email]
- condition {
- title = "automation_sa_delegated_grants"
- description = "Automation service account delegated grants."
- expression = format(
- "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])",
- join(",", formatlist("'%s'", [
- "roles/billing.costsManager",
- "roles/billing.user",
- ]
- ))
- )
- }
- depends_on = [module.billing-organization-ext]
-}
-
# standalone billing account
resource "google_billing_account_iam_member" "billing_ext_admin" {
for_each = toset(
- local.billing_ext ? local.billing_ext_admins : []
+ !var.billing_account.is_org_level ? local.billing_ext_admins : []
)
billing_account_id = var.billing_account.id
role = "roles/billing.admin"
@@ -110,7 +72,7 @@ resource "google_billing_account_iam_member" "billing_ext_admin" {
resource "google_billing_account_iam_member" "billing_ext_cost_manager" {
for_each = toset(
- local.billing_ext ? local.billing_ext_admins : []
+ !var.billing_account.is_org_level ? local.billing_ext_admins : []
)
billing_account_id = var.billing_account.id
role = "roles/billing.costsManager"
diff --git a/fast/stages/00-bootstrap/cicd.tf b/fast/stages/0-bootstrap/cicd.tf
similarity index 86%
rename from fast/stages/00-bootstrap/cicd.tf
rename to fast/stages/0-bootstrap/cicd.tf
index 7cdae41c98..2b2a3df95f 100644
--- a/fast/stages/00-bootstrap/cicd.tf
+++ b/fast/stages/0-bootstrap/cicd.tf
@@ -17,6 +17,16 @@
# tfdoc:file:description Workload Identity Federation configurations for CI/CD.
locals {
+ cicd_providers = {
+ for k, v in google_iam_workload_identity_pool_provider.default :
+ k => {
+ issuer = local.identity_providers[k].issuer
+ issuer_uri = local.identity_providers[k].issuer_uri
+ name = v.name
+ principal_tpl = local.identity_providers[k].principal_tpl
+ principalset_tpl = local.identity_providers[k].principalset_tpl
+ }
+ }
cicd_repositories = {
for k, v in coalesce(var.cicd_repositories, {}) : k => v
if(
@@ -32,18 +42,13 @@ locals {
)
}
cicd_workflow_providers = {
- bootstrap = "00-bootstrap-providers.tf"
- cicd = "00-cicd-providers.tf"
- resman = "01-resman-providers.tf"
+ bootstrap = "0-bootstrap-providers.tf"
+ resman = "1-resman-providers.tf"
}
cicd_workflow_var_files = {
bootstrap = []
- cicd = [
- "00-bootstrap.auto.tfvars.json",
- "globals.auto.tfvars.json"
- ]
resman = [
- "00-bootstrap.auto.tfvars.json",
+ "0-bootstrap.auto.tfvars.json",
"globals.auto.tfvars.json"
]
}
@@ -69,7 +74,7 @@ module "automation-tf-cicd-repo" {
]
}
triggers = {
- "fast-00-${each.key}" = {
+ "fast-0-${each.key}" = {
filename = ".cloudbuild/workflow.yaml"
included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
service_account = module.automation-tf-cicd-sa[each.key].id
diff --git a/fast/stages/00-bootstrap/diagram.png b/fast/stages/0-bootstrap/diagram.png
similarity index 100%
rename from fast/stages/00-bootstrap/diagram.png
rename to fast/stages/0-bootstrap/diagram.png
diff --git a/fast/stages/00-bootstrap/diagram.svg b/fast/stages/0-bootstrap/diagram.svg
similarity index 100%
rename from fast/stages/00-bootstrap/diagram.svg
rename to fast/stages/0-bootstrap/diagram.svg
diff --git a/fast/stages/00-bootstrap/groups.gif b/fast/stages/0-bootstrap/groups.gif
similarity index 100%
rename from fast/stages/00-bootstrap/groups.gif
rename to fast/stages/0-bootstrap/groups.gif
diff --git a/fast/stages/00-bootstrap/identity-providers.tf b/fast/stages/0-bootstrap/identity-providers.tf
similarity index 100%
rename from fast/stages/00-bootstrap/identity-providers.tf
rename to fast/stages/0-bootstrap/identity-providers.tf
diff --git a/fast/stages/00-bootstrap/log-export.tf b/fast/stages/0-bootstrap/log-export.tf
similarity index 82%
rename from fast/stages/00-bootstrap/log-export.tf
rename to fast/stages/0-bootstrap/log-export.tf
index 1c9f5a87a3..b9b5da42fa 100644
--- a/fast/stages/00-bootstrap/log-export.tf
+++ b/fast/stages/0-bootstrap/log-export.tf
@@ -17,6 +17,16 @@
# tfdoc:file:description Audit log project and sink.
locals {
+ log_sink_destinations = merge(
+ # use the same dataset for all sinks with `bigquery` as destination
+ { for k, v in var.log_sinks : k => module.log-export-dataset.0 if v.type == "bigquery" },
+ # use the same gcs bucket for all sinks with `storage` as destination
+ { for k, v in var.log_sinks : k => module.log-export-gcs.0 if v.type == "storage" },
+ # use separate pubsub topics and logging buckets for sinks with
+ # destination `pubsub` and `logging`
+ module.log-export-pubsub,
+ module.log-export-logbucket
+ )
log_types = toset([for k, v in var.log_sinks : v.type])
}
diff --git a/fast/stages/00-bootstrap/main.tf b/fast/stages/0-bootstrap/main.tf
similarity index 78%
rename from fast/stages/00-bootstrap/main.tf
rename to fast/stages/0-bootstrap/main.tf
index 839019f3cf..dba2ed089c 100644
--- a/fast/stages/00-bootstrap/main.tf
+++ b/fast/stages/0-bootstrap/main.tf
@@ -28,10 +28,6 @@ locals {
for k, v in local.groups :
k => "group:${v}"
}
- # convenience flags that express where billing account resides
- billing_ext = var.billing_account.organization_id == null
- billing_org = var.billing_account.organization_id == var.organization.id
- billing_org_ext = !local.billing_ext && !local.billing_org
# naming: environment used in most resource names
prefix = join("-", compact([var.prefix, "prod"]))
}
diff --git a/fast/stages/00-bootstrap/organization.tf b/fast/stages/0-bootstrap/organization.tf
similarity index 89%
rename from fast/stages/00-bootstrap/organization.tf
rename to fast/stages/0-bootstrap/organization.tf
index 33b87820d3..36b2ac344c 100644
--- a/fast/stages/00-bootstrap/organization.tf
+++ b/fast/stages/0-bootstrap/organization.tf
@@ -24,7 +24,10 @@ locals {
"domain:${var.organization.domain}"
]
"roles/logging.admin" = concat(
- [module.automation-tf-bootstrap-sa.iam_email],
+ [
+ module.automation-tf-bootstrap-sa.iam_email,
+ module.automation-tf-resman-sa.iam_email
+ ],
local._iam_bootstrap_user
)
"roles/owner" = local._iam_bootstrap_user
@@ -35,12 +38,11 @@ locals {
[module.automation-tf-bootstrap-sa.iam_email],
local._iam_bootstrap_user
)
- # the following is useful if roles/browser is not desirable
- # "roles/resourcemanager.organizationViewer" = [
- # "domain:${var.organization.domain}"
- # ]
"roles/resourcemanager.projectCreator" = concat(
- [module.automation-tf-bootstrap-sa.iam_email],
+ [
+ module.automation-tf-bootstrap-sa.iam_email,
+ module.automation-tf-resman-sa.iam_email
+ ],
local._iam_bootstrap_user
)
"roles/resourcemanager.projectMover" = [
@@ -78,8 +80,12 @@ locals {
local.groups_iam.gcp-security-admins,
module.automation-tf-resman-sa.iam_email
]
+ # the following is useful if roles/browser is not desirable
+ # "roles/resourcemanager.organizationViewer" = [
+ # "domain:${var.organization.domain}"
+ # ]
},
- local.billing_org ? {
+ var.billing_account.is_org_level ? {
"roles/billing.admin" = [
local.groups_iam.gcp-billing-admins,
local.groups_iam.gcp-organization-admins,
@@ -115,16 +121,6 @@ locals {
iam_roles_additive = distinct(concat(
keys(local._iam_additive), keys(var.iam_additive)
))
- log_sink_destinations = merge(
- # use the same dataset for all sinks with `bigquery` as destination
- { for k, v in var.log_sinks : k => module.log-export-dataset.0 if v.type == "bigquery" },
- # use the same gcs bucket for all sinks with `storage` as destination
- { for k, v in var.log_sinks : k => module.log-export-gcs.0 if v.type == "storage" },
- # use separate pubsub topics and logging buckets for sinks with
- # destination `pubsub` and `logging`
- module.log-export-pubsub,
- module.log-export-logbucket
- )
}
module "organization" {
@@ -220,8 +216,9 @@ resource "google_organization_iam_binding" "org_admin_delegated" {
"roles/compute.orgFirewallPolicyAdmin",
"roles/compute.xpnAdmin",
"roles/orgpolicy.policyAdmin",
+ "roles/resourcemanager.organizationViewer",
],
- local.billing_org ? [
+ var.billing_account.is_org_level ? [
"roles/billing.admin",
"roles/billing.costsManager",
"roles/billing.user",
diff --git a/fast/stages/00-bootstrap/outputs-files.tf b/fast/stages/0-bootstrap/outputs-files.tf
similarity index 97%
rename from fast/stages/00-bootstrap/outputs-files.tf
rename to fast/stages/0-bootstrap/outputs-files.tf
index ded88cd56d..90e195b53f 100644
--- a/fast/stages/00-bootstrap/outputs-files.tf
+++ b/fast/stages/0-bootstrap/outputs-files.tf
@@ -26,7 +26,7 @@ resource "local_file" "providers" {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/00-bootstrap.auto.tfvars.json"
+ filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/0-bootstrap.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/00-bootstrap/outputs-gcs.tf b/fast/stages/0-bootstrap/outputs-gcs.tf
similarity index 96%
rename from fast/stages/00-bootstrap/outputs-gcs.tf
rename to fast/stages/0-bootstrap/outputs-gcs.tf
index 2c281d4ccb..0aded986a9 100644
--- a/fast/stages/00-bootstrap/outputs-gcs.tf
+++ b/fast/stages/0-bootstrap/outputs-gcs.tf
@@ -26,7 +26,7 @@ resource "google_storage_bucket_object" "providers" {
resource "google_storage_bucket_object" "tfvars" {
bucket = module.automation-tf-output-gcs.name
- name = "tfvars/00-bootstrap.auto.tfvars.json"
+ name = "tfvars/0-bootstrap.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/00-bootstrap/outputs.tf b/fast/stages/0-bootstrap/outputs.tf
similarity index 77%
rename from fast/stages/00-bootstrap/outputs.tf
rename to fast/stages/0-bootstrap/outputs.tf
index 73dd64f4e9..364abd6840 100644
--- a/fast/stages/00-bootstrap/outputs.tf
+++ b/fast/stages/0-bootstrap/outputs.tf
@@ -21,7 +21,7 @@ locals {
for k, v in local.cicd_repositories : k => templatefile(
"${path.module}/templates/workflow-${v.type}.yaml", {
identity_provider = try(
- local.wif_providers[v["identity_provider"]].name, ""
+ local.cicd_providers[v["identity_provider"]].name, ""
)
outputs_bucket = module.automation-tf-output-gcs.name
service_account = try(
@@ -38,19 +38,26 @@ locals {
k => try(module.organization.custom_role_id[v], null)
}
providers = {
- "00-bootstrap" = templatefile(local._tpl_providers, {
- bucket = module.automation-tf-bootstrap-gcs.name
- name = "bootstrap"
- sa = module.automation-tf-bootstrap-sa.email
+ "0-bootstrap" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.automation-tf-bootstrap-gcs.name
+ name = "bootstrap"
+ sa = module.automation-tf-bootstrap-sa.email
})
- "00-cicd" = templatefile(local._tpl_providers, {
- bucket = module.automation-tf-cicd-gcs.name
- name = "cicd"
- sa = module.automation-tf-cicd-provisioning-sa.email
+ "1-resman" = templatefile(local._tpl_providers, {
+ backend_extra = null
+ bucket = module.automation-tf-resman-gcs.name
+ name = "resman"
+ sa = module.automation-tf-resman-sa.email
})
- "01-resman" = templatefile(local._tpl_providers, {
+ "0-bootstrap-tenant" = templatefile(local._tpl_providers, {
+ backend_extra = join("\n", [
+ "# remove the newline between quotes and set the tenant name as prefix",
+ "prefix = \"",
+ "\""
+ ])
bucket = module.automation-tf-resman-gcs.name
- name = "resman"
+ name = "bootstrap-tenant"
sa = module.automation-tf-resman-sa.email
})
}
@@ -59,7 +66,7 @@ locals {
federated_identity_pool = try(
google_iam_workload_identity_pool.default.0.name, null
)
- federated_identity_providers = local.wif_providers
+ federated_identity_providers = local.cicd_providers
outputs_bucket = module.automation-tf-output-gcs.name
project_id = module.automation-project.project_id
project_number = module.automation-project.number
@@ -74,16 +81,6 @@ locals {
organization = var.organization
prefix = var.prefix
}
- wif_providers = {
- for k, v in google_iam_workload_identity_pool_provider.default :
- k => {
- issuer = local.identity_providers[k].issuer
- issuer_uri = local.identity_providers[k].issuer_uri
- name = v.name
- principal_tpl = local.identity_providers[k].principal_tpl
- principalset_tpl = local.identity_providers[k].principalset_tpl
- }
- }
}
output "automation" {
@@ -102,7 +99,7 @@ output "cicd_repositories" {
for k, v in local.cicd_repositories : k => {
branch = v.branch
name = v.name
- provider = try(local.wif_providers[v.identity_provider].name, null)
+ provider = try(local.cicd_providers[v.identity_provider].name, null)
service_account = try(module.automation-tf-cicd-sa[k].email, null)
}
}
@@ -119,7 +116,7 @@ output "federated_identity" {
pool = try(
google_iam_workload_identity_pool.default.0.name, null
)
- providers = local.wif_providers
+ providers = local.cicd_providers
}
}
@@ -149,7 +146,6 @@ output "service_accounts" {
description = "Automation service accounts created by this stage."
value = {
bootstrap = module.automation-tf-bootstrap-sa.email
- cicd = module.automation-tf-cicd-provisioning-sa.email
resman = module.automation-tf-resman-sa.email
}
}
diff --git a/fast/stages/0-bootstrap/templates/providers.tf.tpl b/fast/stages/0-bootstrap/templates/providers.tf.tpl
new file mode 100644
index 0000000000..d1c224c5c1
--- /dev/null
+++ b/fast/stages/0-bootstrap/templates/providers.tf.tpl
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+terraform {
+ backend "gcs" {
+ bucket = "${bucket}"
+ impersonate_service_account = "${sa}"
+ %{~ if backend_extra != null ~}
+ ${indent(4, backend_extra)}
+ %{~ endif ~}
+ }
+}
+provider "google" {
+ impersonate_service_account = "${sa}"
+}
+provider "google-beta" {
+ impersonate_service_account = "${sa}"
+}
+
+# end provider.tf for ${name}
diff --git a/fast/stages/0-bootstrap/templates/workflow-github.yaml b/fast/stages/0-bootstrap/templates/workflow-github.yaml
new file mode 100644
index 0000000000..8a946d84c8
--- /dev/null
+++ b/fast/stages/0-bootstrap/templates/workflow-github.yaml
@@ -0,0 +1,186 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: "FAST ${stage_name} stage"
+
+on:
+ pull_request:
+ branches:
+ - main
+ types:
+ - closed
+ - opened
+ - synchronize
+
+env:
+ FAST_OUTPUTS_BUCKET: ${outputs_bucket}
+ FAST_SERVICE_ACCOUNT: ${service_account}
+ FAST_WIF_PROVIDER: ${identity_provider}
+ SSH_AUTH_SOCK: /tmp/ssh_agent.sock
+ TF_PROVIDERS_FILE: ${tf_providers_file}
+ TF_VAR_FILES: ${tf_var_files == [] ? "''" : join("\n ", tf_var_files)}
+ TF_VERSION: 1.3.2
+
+jobs:
+ fast-pr:
+ permissions:
+ contents: read
+ id-token: write
+ issues: write
+ pull-requests: write
+ runs-on: ubuntu-latest
+ steps:
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@v3
+
+ # set up SSH key authentication to the modules repository
+ - id: ssh-config
+ name: Configure SSH authentication
+ run: |
+ ssh-agent -a "$SSH_AUTH_SOCK" > /dev/null
+ ssh-add - <<< "$${{ secrets.CICD_MODULES_KEY }}"
+
+ # set up authentication via Workload identity Federation
+ - id: gcp-auth
+ name: Authenticate to Google Cloud
+ uses: google-github-actions/auth@v0
+ with:
+ workload_identity_provider: $${{ env.FAST_WIF_PROVIDER }}
+ service_account: $${{ env.FAST_SERVICE_ACCOUNT }}
+ access_token_lifetime: 3600s
+
+ - id: gcp-sdk
+ name: Set up Cloud SDK
+ uses: google-github-actions/setup-gcloud@v0
+ with:
+ install_components: alpha
+
+ # copy provider and tfvars files
+ - id: tf-config
+ name: Copy Terraform output files
+ run: |
+ gcloud alpha storage cp -r \
+ "gs://$${{env.FAST_OUTPUTS_BUCKET}}/providers/$${{env.TF_PROVIDERS_FILE}}" ./
+ gcloud alpha storage cp -r \
+ "gs://$${{env.FAST_OUTPUTS_BUCKET}}/tfvars" ./
+ for f in $${{env.TF_VAR_FILES}}; do
+ ln -s "tfvars/$f" ./
+ done
+
+ - id: tf-setup
+ name: Set up Terraform
+ uses: hashicorp/setup-terraform@v2.0.3
+ with:
+ terraform_version: $${{ env.TF_VERSION }}
+
+ # run Terraform init/validate/plan
+ - id: tf-init
+ name: Terraform init
+ run: |
+ terraform init -no-color
+
+ - id: tf-validate
+ name: Terraform validate
+ run: terraform validate -no-color
+
+ - id: tf-plan
+ name: Terraform plan
+ continue-on-error: true
+ run: |
+ terraform plan -input=false -out ../plan.out -no-color
+
+ - id: tf-apply
+ if: github.event.pull_request.merged == true && success()
+ name: Terraform apply
+ continue-on-error: true
+ run: |
+ terraform apply -input=false -auto-approve -no-color ../plan.out
+
+ - id: pr-comment
+ name: Post comment to Pull Request
+ continue-on-error: true
+ uses: actions/github-script@v6
+ if: github.event_name == 'pull_request'
+ env:
+ PLAN: $${{ steps.tf-plan.outputs.stdout }}\n$${{ steps.tf-plan.outputs.stderr }}
+ with:
+ script: |
+ const output = `### Terraform Initialization \`$${{ steps.tf-init.outcome }}\`
+
+ ### Terraform Validation \`$${{ steps.tf-validate.outcome }}\`
+
+ organization
| google_billing_account_iam_member
|
-| [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder
· gcs
· iam-service-account
| |
+| [billing.tf](./billing.tf) | Billing resources for external billing use cases. | | google_billing_account_iam_member
|
+| [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder
· gcs
· iam-service-account
| google_organization_iam_member
|
| [branch-gke.tf](./branch-gke.tf) | GKE multitenant stage resources. | folder
· gcs
· iam-service-account
| |
| [branch-networking.tf](./branch-networking.tf) | Networking stage resources. | folder
· gcs
· iam-service-account
| |
-| [branch-project-factory.tf](./branch-project-factory.tf) | Project factory stage resources. | gcs
· iam-service-account
| |
+| [branch-project-factory.tf](./branch-project-factory.tf) | Project factory stage resources. | gcs
· iam-service-account
| google_organization_iam_member
|
| [branch-sandbox.tf](./branch-sandbox.tf) | Sandbox stage resources. | folder
· gcs
· iam-service-account
| |
| [branch-security.tf](./branch-security.tf) | Security stage resources. | folder
· gcs
· iam-service-account
| |
| [branch-teams.tf](./branch-teams.tf) | Team stage resources. | folder
· gcs
· iam-service-account
| |
@@ -170,7 +174,7 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
| [cicd-project-factory.tf](./cicd-project-factory.tf) | CI/CD resources for the teams branch. | iam-service-account
· source-repository
| |
| [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | iam-service-account
· source-repository
| |
| [main.tf](./main.tf) | Module-level locals and resources. | | |
-| [organization.tf](./organization.tf) | Organization policies. | organization
| google_organization_iam_member
|
+| [organization.tf](./organization.tf) | Organization policies. | organization
| |
| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | local_file
|
| [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | | google_storage_bucket_object
|
| [outputs.tf](./outputs.tf) | Module outputs. | | |
@@ -180,34 +184,34 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 00-bootstrap
|
-| [billing_account](variables.tf#L38) | Billing account id and organization id ('nnnnnnnn' or null). | object({…})
| ✓ | | 00-bootstrap
|
-| [organization](variables.tf#L197) | Organization details. | object({…})
| ✓ | | 00-bootstrap
|
-| [prefix](variables.tf#L221) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 00-bootstrap
|
-| [cicd_repositories](variables.tf#L47) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…})
| | null
| |
-| [custom_roles](variables.tf#L129) | Custom roles defined at the org level, in key => id format. | object({…})
| | null
| 00-bootstrap
|
-| [data_dir](variables.tf#L138) | Relative path for the folder storing configuration data. | string
| | "data"
| |
-| [fast_features](variables.tf#L144) | Selective control for top-level FAST features. | object({…})
| | {…}
| 00-bootstrap
|
-| [groups](variables.tf#L164) | Group names to grant organization-level permissions. | map(string)
| | {…}
| 00-bootstrap
|
-| [locations](variables.tf#L179) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…})
| | {…}
| 00-bootstrap
|
-| [organization_policy_configs](variables.tf#L207) | Organization policies customization. | object({…})
| | null
| |
-| [outputs_location](variables.tf#L215) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string
| | null
| |
-| [tag_names](variables.tf#L232) | Customized names for resource management tags. | object({…})
| | {…}
| |
-| [team_folders](variables.tf#L249) | Team folders to be created. Format is described in a code comment. | map(object({…}))
| | null
| |
+| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 0-bootstrap
|
+| [billing_account](variables.tf#L38) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…})
| ✓ | | 0-bootstrap
|
+| [organization](variables.tf#L193) | Organization details. | object({…})
| ✓ | | 0-bootstrap
|
+| [prefix](variables.tf#L217) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 0-bootstrap
|
+| [cicd_repositories](variables.tf#L51) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…})
| | null
| |
+| [custom_roles](variables.tf#L133) | Custom roles defined at the org level, in key => id format. | object({…})
| | null
| 0-bootstrap
|
+| [data_dir](variables.tf#L142) | Relative path for the folder storing configuration data. | string
| | "data"
| |
+| [fast_features](variables.tf#L148) | Selective control for top-level FAST features. | object({…})
| | {}
| 0-0-bootstrap
|
+| [groups](variables.tf#L162) | Group names to grant organization-level permissions. | object({…})
| | {}
| 0-bootstrap
|
+| [locations](variables.tf#L175) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…})
| | {…}
| 0-bootstrap
|
+| [organization_policy_configs](variables.tf#L203) | Organization policies customization. | object({…})
| | null
| |
+| [outputs_location](variables.tf#L211) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string
| | null
| |
+| [tag_names](variables.tf#L228) | Customized names for resource management tags. | object({…})
| | {…}
| |
+| [team_folders](variables.tf#L247) | Team folders to be created. Format is described in a code comment. | map(object({…}))
| | null
| |
## Outputs
| name | description | sensitive | consumers |
|---|---|:---:|---|
-| [cicd_repositories](outputs.tf#L197) | WIF configuration for CI/CD repositories. | | |
-| [dataplatform](outputs.tf#L211) | Data for the Data Platform stage. | | |
-| [gke_multitenant](outputs.tf#L227) | Data for the GKE multitenant stage. | | 03-gke-multitenant
|
-| [networking](outputs.tf#L248) | Data for the networking stage. | | |
-| [project_factories](outputs.tf#L257) | Data for the project factories stage. | | |
-| [providers](outputs.tf#L272) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking
· 02-security
· 03-dataplatform
· xx-sandbox
· xx-teams
|
-| [sandbox](outputs.tf#L279) | Data for the sandbox stage. | | xx-sandbox
|
-| [security](outputs.tf#L293) | Data for the networking stage. | | 02-security
|
-| [teams](outputs.tf#L303) | Data for the teams stage. | | |
-| [tfvars](outputs.tf#L315) | Terraform variable files for the following stages. | ✓ | |
+| [cicd_repositories](outputs.tf#L210) | WIF configuration for CI/CD repositories. | | |
+| [dataplatform](outputs.tf#L224) | Data for the Data Platform stage. | | |
+| [gke_multitenant](outputs.tf#L240) | Data for the GKE multitenant stage. | | 03-gke-multitenant
|
+| [networking](outputs.tf#L261) | Data for the networking stage. | | |
+| [project_factories](outputs.tf#L270) | Data for the project factories stage. | | |
+| [providers](outputs.tf#L285) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking
· 02-security
· 03-dataplatform
· xx-sandbox
· xx-teams
|
+| [sandbox](outputs.tf#L292) | Data for the sandbox stage. | | xx-sandbox
|
+| [security](outputs.tf#L306) | Data for the networking stage. | | 02-security
|
+| [teams](outputs.tf#L316) | Data for the teams stage. | | |
+| [tfvars](outputs.tf#L328) | Terraform variable files for the following stages. | ✓ | |
diff --git a/fast/stages/01-resman/billing.tf b/fast/stages/1-resman/billing.tf
similarity index 77%
rename from fast/stages/01-resman/billing.tf
rename to fast/stages/1-resman/billing.tf
index fe497c7c34..ba20ab0534 100644
--- a/fast/stages/01-resman/billing.tf
+++ b/fast/stages/1-resman/billing.tf
@@ -34,23 +34,11 @@ locals {
# billing account in same org (resources is in the organization.tf file)
-# billing account in a different org
-
-module "billing-organization-ext" {
- source = "../../../modules/organization"
- count = local.billing_org_ext ? 1 : 0
- organization_id = "organizations/${var.billing_account.organization_id}"
- iam_additive = {
- "roles/billing.user" = local.billing_ext_users
- "roles/billing.costsManager" = local.billing_ext_users
- }
-}
-
# standalone billing account
resource "google_billing_account_iam_member" "billing_ext_admin" {
for_each = toset(
- local.billing_ext ? local.billing_ext_users : []
+ !var.billing_account.is_org_level ? local.billing_ext_users : []
)
billing_account_id = var.billing_account.id
role = "roles/billing.user"
@@ -59,7 +47,7 @@ resource "google_billing_account_iam_member" "billing_ext_admin" {
resource "google_billing_account_iam_member" "billing_ext_costsmanager" {
for_each = toset(
- local.billing_ext ? local.billing_ext_users : []
+ !var.billing_account.is_org_level ? local.billing_ext_users : []
)
billing_account_id = var.billing_account.id
role = "roles/billing.costsManager"
diff --git a/fast/stages/01-resman/branch-data-platform.tf b/fast/stages/1-resman/branch-data-platform.tf
similarity index 86%
rename from fast/stages/01-resman/branch-data-platform.tf
rename to fast/stages/1-resman/branch-data-platform.tf
index 66cc9fbb08..7a93e7a54b 100644
--- a/fast/stages/01-resman/branch-data-platform.tf
+++ b/fast/stages/1-resman/branch-data-platform.tf
@@ -137,3 +137,22 @@ module "branch-dp-prod-gcs" {
"roles/storage.objectAdmin" = [module.branch-dp-prod-sa.0.iam_email]
}
}
+
+resource "google_organization_iam_member" "org_policy_admin_dp" {
+ for_each = !var.fast_features.data_platform ? {} : {
+ data-dev = ["data", "development", module.branch-dp-dev-sa.0.iam_email]
+ data-prod = ["data", "production", module.branch-dp-prod-sa.0.iam_email]
+ }
+ org_id = var.organization.id
+ role = "roles/orgpolicy.policyAdmin"
+ member = each.value.2
+ condition {
+ title = "org_policy_tag_dp_scoped"
+ description = "Org policy tag scoped grant for ${each.value.0}/${each.value.1}."
+ expression = <<-END
+ resource.matchTag('${var.organization.id}/${var.tag_names.context}', '${each.value.0}')
+ &&
+ resource.matchTag('${var.organization.id}/${var.tag_names.environment}', '${each.value.1}')
+ END
+ }
+}
diff --git a/fast/stages/01-resman/branch-gke.tf b/fast/stages/1-resman/branch-gke.tf
similarity index 94%
rename from fast/stages/01-resman/branch-gke.tf
rename to fast/stages/1-resman/branch-gke.tf
index 84ca41ed59..76777d8fca 100644
--- a/fast/stages/01-resman/branch-gke.tf
+++ b/fast/stages/1-resman/branch-gke.tf
@@ -77,7 +77,11 @@ module "branch-gke-dev-sa" {
prefix = var.prefix
iam = {
"roles/iam.serviceAccountTokenCreator" = concat(
- ["group:${local.groups.gcp-devops}"],
+ (
+ local.groups.gcp-devops == null
+ ? []
+ : ["group:${local.groups.gcp-devops}"]
+ ),
compact([
try(module.branch-gke-dev-sa-cicd.0.iam_email, null)
])
@@ -97,7 +101,11 @@ module "branch-gke-prod-sa" {
prefix = var.prefix
iam = {
"roles/iam.serviceAccountTokenCreator" = concat(
- ["group:${local.groups.gcp-devops}"],
+ (
+ local.groups.gcp-devops == null
+ ? []
+ : ["group:${local.groups.gcp-devops}"]
+ ),
compact([
try(module.branch-gke-prod-sa-cicd.0.iam_email, null)
])
diff --git a/fast/stages/01-resman/branch-networking.tf b/fast/stages/1-resman/branch-networking.tf
similarity index 98%
rename from fast/stages/01-resman/branch-networking.tf
rename to fast/stages/1-resman/branch-networking.tf
index 530cf6b09f..1fd7a6b3d9 100644
--- a/fast/stages/01-resman/branch-networking.tf
+++ b/fast/stages/1-resman/branch-networking.tf
@@ -20,7 +20,7 @@ module "branch-network-folder" {
source = "../../../modules/folder"
parent = "organizations/${var.organization.id}"
name = "Networking"
- group_iam = {
+ group_iam = local.groups.gcp-network-admins == null ? {} : {
(local.groups.gcp-network-admins) = [
# add any needed roles for resources/services not managed via Terraform,
# or replace editor with ~viewer if no broad resource management needed
diff --git a/fast/stages/01-resman/branch-project-factory.tf b/fast/stages/1-resman/branch-project-factory.tf
similarity index 78%
rename from fast/stages/01-resman/branch-project-factory.tf
rename to fast/stages/1-resman/branch-project-factory.tf
index 41651a28c3..d74a8acb10 100644
--- a/fast/stages/01-resman/branch-project-factory.tf
+++ b/fast/stages/1-resman/branch-project-factory.tf
@@ -79,3 +79,22 @@ module "branch-pf-prod-gcs" {
"roles/storage.objectAdmin" = [module.branch-pf-prod-sa.0.iam_email]
}
}
+
+resource "google_organization_iam_member" "org_policy_admin_pf" {
+ for_each = !var.fast_features.project_factory ? {} : {
+ pf-dev = ["teams", "development", module.branch-pf-dev-sa.0.iam_email]
+ pf-prod = ["teams", "production", module.branch-pf-prod-sa.0.iam_email]
+ }
+ org_id = var.organization.id
+ role = "roles/orgpolicy.policyAdmin"
+ member = each.value.2
+ condition {
+ title = "org_policy_tag_pf_scoped"
+ description = "Org policy tag scoped grant for ${each.value.0}/${each.value.1}."
+ expression = <<-END
+ resource.matchTag('${var.organization.id}/${var.tag_names.context}', '${each.value.0}')
+ &&
+ resource.matchTag('${var.organization.id}/${var.tag_names.environment}', '${each.value.1}')
+ END
+ }
+}
diff --git a/fast/stages/01-resman/branch-sandbox.tf b/fast/stages/1-resman/branch-sandbox.tf
similarity index 100%
rename from fast/stages/01-resman/branch-sandbox.tf
rename to fast/stages/1-resman/branch-sandbox.tf
diff --git a/fast/stages/01-resman/branch-security.tf b/fast/stages/1-resman/branch-security.tf
similarity index 96%
rename from fast/stages/01-resman/branch-security.tf
rename to fast/stages/1-resman/branch-security.tf
index c7b4fc9708..4b0c0fb131 100644
--- a/fast/stages/01-resman/branch-security.tf
+++ b/fast/stages/1-resman/branch-security.tf
@@ -20,7 +20,7 @@ module "branch-security-folder" {
source = "../../../modules/folder"
parent = "organizations/${var.organization.id}"
name = "Security"
- group_iam = {
+ group_iam = local.groups.gcp-security-admins == null ? {} : {
(local.groups.gcp-security-admins) = [
# add any needed roles for resources/services not managed via Terraform,
# e.g.
@@ -51,7 +51,7 @@ module "branch-security-folder" {
module "branch-security-sa" {
source = "../../../modules/iam-service-account"
project_id = var.automation.project_id
- name = "prod-resman-sec-0"
+ name = "security-0"
display_name = "Terraform resman security service account."
prefix = var.prefix
iam = {
diff --git a/fast/stages/01-resman/branch-teams.tf b/fast/stages/1-resman/branch-teams.tf
similarity index 100%
rename from fast/stages/01-resman/branch-teams.tf
rename to fast/stages/1-resman/branch-teams.tf
diff --git a/fast/stages/01-resman/cicd-data-platform.tf b/fast/stages/1-resman/cicd-data-platform.tf
similarity index 99%
rename from fast/stages/01-resman/cicd-data-platform.tf
rename to fast/stages/1-resman/cicd-data-platform.tf
index 5b07883c44..e69fd5bc1e 100644
--- a/fast/stages/01-resman/cicd-data-platform.tf
+++ b/fast/stages/1-resman/cicd-data-platform.tf
@@ -103,7 +103,7 @@ module "branch-dp-dev-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
@@ -146,7 +146,7 @@ module "branch-dp-prod-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
diff --git a/fast/stages/01-resman/cicd-gke.tf b/fast/stages/1-resman/cicd-gke.tf
similarity index 99%
rename from fast/stages/01-resman/cicd-gke.tf
rename to fast/stages/1-resman/cicd-gke.tf
index fa4f8767ca..4388a3ac55 100644
--- a/fast/stages/01-resman/cicd-gke.tf
+++ b/fast/stages/1-resman/cicd-gke.tf
@@ -103,7 +103,7 @@ module "branch-gke-dev-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
@@ -146,7 +146,7 @@ module "branch-gke-prod-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
diff --git a/fast/stages/01-resman/cicd-networking.tf b/fast/stages/1-resman/cicd-networking.tf
similarity index 99%
rename from fast/stages/01-resman/cicd-networking.tf
rename to fast/stages/1-resman/cicd-networking.tf
index 894348ff3b..245d5ed025 100644
--- a/fast/stages/01-resman/cicd-networking.tf
+++ b/fast/stages/1-resman/cicd-networking.tf
@@ -65,7 +65,7 @@ module "branch-network-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
diff --git a/fast/stages/01-resman/cicd-project-factory.tf b/fast/stages/1-resman/cicd-project-factory.tf
similarity index 99%
rename from fast/stages/01-resman/cicd-project-factory.tf
rename to fast/stages/1-resman/cicd-project-factory.tf
index 8f357ce6c0..1e2b456531 100644
--- a/fast/stages/01-resman/cicd-project-factory.tf
+++ b/fast/stages/1-resman/cicd-project-factory.tf
@@ -114,7 +114,7 @@ module "branch-pf-dev-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
@@ -162,7 +162,7 @@ module "branch-pf-prod-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
diff --git a/fast/stages/01-resman/cicd-security.tf b/fast/stages/1-resman/cicd-security.tf
similarity index 99%
rename from fast/stages/01-resman/cicd-security.tf
rename to fast/stages/1-resman/cicd-security.tf
index dd27a47331..c35bfbfbb6 100644
--- a/fast/stages/01-resman/cicd-security.tf
+++ b/fast/stages/1-resman/cicd-security.tf
@@ -65,7 +65,7 @@ module "branch-security-sa-cicd" {
each.value.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
- "roles/iam.serviceAccountUser" = local.automation_resman_sa
+ "roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
diff --git a/fast/stages/1-resman/data/org-policies/compute.yaml b/fast/stages/1-resman/data/org-policies/compute.yaml
new file mode 100644
index 0000000000..0d27ac426d
--- /dev/null
+++ b/fast/stages/1-resman/data/org-policies/compute.yaml
@@ -0,0 +1,73 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+compute.disableGuestAttributesAccess:
+ enforce: true
+
+compute.requireOsLogin:
+ enforce: true
+
+compute.restrictLoadBalancerCreationForTypes:
+ allow:
+ values:
+ - in:INTERNAL
+
+compute.skipDefaultNetworkCreation:
+ enforce: true
+
+compute.vmExternalIpAccess:
+ deny:
+ all: true
+
+
+# compute.disableInternetNetworkEndpointGroup:
+# enforce: true
+
+# compute.disableNestedVirtualization:
+# enforce: true
+
+# compute.disableSerialPortAccess:
+# enforce: true
+
+# compute.restrictCloudNATUsage:
+# deny:
+# all: true
+
+# compute.restrictDedicatedInterconnectUsage:
+# deny:
+# all: true
+
+# compute.restrictPartnerInterconnectUsage:
+# deny:
+# all: true
+
+# compute.restrictProtocolForwardingCreationForTypes:
+# deny:
+# all: true
+
+# compute.restrictSharedVpcHostProjects:
+# deny:
+# all: true
+
+# compute.restrictSharedVpcSubnetworks:
+# deny:
+# all: true
+
+# compute.restrictVpcPeering:
+# deny:
+# all: true
+
+# compute.restrictVpnPeerIPs:
+# deny:
+# all: true
+
+# compute.restrictXpnProjectLienRemoval:
+# enforce: true
+
+# compute.setNewProjectDefaultToZonalDNSOnly:
+# enforce: true
+
+# compute.vmCanIpForward:
+# deny:
+# all: true
diff --git a/fast/stages/1-resman/data/org-policies/iam.yaml b/fast/stages/1-resman/data/org-policies/iam.yaml
new file mode 100644
index 0000000000..4d83f827fe
--- /dev/null
+++ b/fast/stages/1-resman/data/org-policies/iam.yaml
@@ -0,0 +1,12 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+iam.automaticIamGrantsForDefaultServiceAccounts:
+ enforce: true
+
+iam.disableServiceAccountKeyCreation:
+ enforce: true
+
+iam.disableServiceAccountKeyUpload:
+ enforce: true
diff --git a/fast/stages/1-resman/data/org-policies/serverless.yaml b/fast/stages/1-resman/data/org-policies/serverless.yaml
new file mode 100644
index 0000000000..de62e6c702
--- /dev/null
+++ b/fast/stages/1-resman/data/org-policies/serverless.yaml
@@ -0,0 +1,26 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+run.allowedIngress:
+ allow:
+ values:
+ - is:internal
+
+# run.allowedVPCEgress:
+# allow:
+# values:
+# - is:private-ranges-only
+
+# cloudfunctions.allowedIngressSettings:
+# allow:
+# values:
+# - is:ALLOW_INTERNAL_ONLY
+
+# cloudfunctions.allowedVpcConnectorEgressSettings:
+# allow:
+# values:
+# - is:PRIVATE_RANGES_ONLY
+
+# cloudfunctions.requireVPCConnector:
+# enforce: true
diff --git a/fast/stages/1-resman/data/org-policies/sql.yaml b/fast/stages/1-resman/data/org-policies/sql.yaml
new file mode 100644
index 0000000000..88b84d9d50
--- /dev/null
+++ b/fast/stages/1-resman/data/org-policies/sql.yaml
@@ -0,0 +1,9 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+sql.restrictAuthorizedNetworks:
+ enforce: true
+
+sql.restrictPublicIp:
+ enforce: true
diff --git a/fast/stages/1-resman/data/org-policies/storage.yaml b/fast/stages/1-resman/data/org-policies/storage.yaml
new file mode 100644
index 0000000000..6c0a673f3a
--- /dev/null
+++ b/fast/stages/1-resman/data/org-policies/storage.yaml
@@ -0,0 +1,6 @@
+# skip boilerplate check
+#
+# sample subset of useful organization policies, edit to suit requirements
+
+storage.uniformBucketLevelAccess:
+ enforce: true
diff --git a/fast/stages/1-resman/diagram.png b/fast/stages/1-resman/diagram.png
new file mode 100644
index 0000000000..d1026318b4
Binary files /dev/null and b/fast/stages/1-resman/diagram.png differ
diff --git a/fast/stages/1-resman/diagram.svg b/fast/stages/1-resman/diagram.svg
new file mode 100644
index 0000000000..541db3f4b8
--- /dev/null
+++ b/fast/stages/1-resman/diagram.svg
@@ -0,0 +1,1340 @@
+
+object({…})
| ✓ | | 00-bootstrap
|
-| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…})
| ✓ | | 00-bootstrap
|
-| [folder_ids](variables.tf#L74) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…})
| ✓ | | 01-resman
|
-| [organization](variables.tf#L102) | Organization details. | object({…})
| ✓ | | 00-bootstrap
|
-| [prefix](variables.tf#L118) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 00-bootstrap
|
-| [custom_adv](variables.tf#L34) | Custom advertisement definitions in name => range format. | map(string)
| | {…}
| |
-| [custom_roles](variables.tf#L51) | Custom roles defined at the org level, in key => id format. | object({…})
| | null
| 00-bootstrap
|
-| [data_dir](variables.tf#L60) | Relative path for the folder storing configuration data for network resources. | string
| | "data"
| |
-| [dns](variables.tf#L66) | Onprem DNS resolvers. | map(list(string))
| | {…}
| |
-| [l7ilb_subnets](variables.tf#L84) | Subnets used for L7 ILBs. | map(list(object({…})))
| | {…}
| |
-| [outputs_location](variables.tf#L112) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
+| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 0-bootstrap
|
+| [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…})
| ✓ | | 0-bootstrap
|
+| [folder_ids](variables.tf#L78) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…})
| ✓ | | 1-resman
|
+| [organization](variables.tf#L106) | Organization details. | object({…})
| ✓ | | 0-bootstrap
|
+| [prefix](variables.tf#L122) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 0-bootstrap
|
+| [custom_adv](variables.tf#L38) | Custom advertisement definitions in name => range format. | map(string)
| | {…}
| |
+| [custom_roles](variables.tf#L55) | Custom roles defined at the org level, in key => id format. | object({…})
| | null
| 0-bootstrap
|
+| [data_dir](variables.tf#L64) | Relative path for the folder storing configuration data for network resources. | string
| | "data"
| |
+| [dns](variables.tf#L70) | Onprem DNS resolvers. | map(list(string))
| | {…}
| |
+| [l7ilb_subnets](variables.tf#L88) | Subnets used for L7 ILBs. | map(list(object({…})))
| | {…}
| |
+| [outputs_location](variables.tf#L116) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
| [peering_configs](variables-peerings.tf#L19) | Peering configurations. | map(object({…}))
| | {…}
| |
-| [psa_ranges](variables.tf#L129) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…})
| | null
| |
-| [region_trigram](variables.tf#L166) | Short names for GCP regions. | map(string)
| | {…}
| |
-| [router_onprem_configs](variables.tf#L175) | Configurations for routers used for onprem connectivity. | map(object({…}))
| | {…}
| |
-| [service_accounts](variables.tf#L193) | Automation service accounts in name => email format. | object({…})
| | null
| 01-resman
|
-| [vpn_onprem_configs](variables.tf#L207) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
+| [psa_ranges](variables.tf#L133) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…})
| | null
| |
+| [region_trigram](variables.tf#L170) | Short names for GCP regions. | map(string)
| | {…}
| |
+| [router_onprem_configs](variables.tf#L179) | Configurations for routers used for onprem connectivity. | map(object({…}))
| | {…}
| |
+| [service_accounts](variables.tf#L197) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
+| [vpn_onprem_configs](variables.tf#L211) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
## Outputs
diff --git a/fast/stages/02-networking-nva/data/cidrs.yaml b/fast/stages/2-networking-a-peering/data/cidrs.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/cidrs.yaml
rename to fast/stages/2-networking-a-peering/data/cidrs.yaml
diff --git a/fast/stages/02-networking-peering/data/dashboards/firewall_insights.json b/fast/stages/2-networking-a-peering/data/dashboards/firewall_insights.json
similarity index 100%
rename from fast/stages/02-networking-peering/data/dashboards/firewall_insights.json
rename to fast/stages/2-networking-a-peering/data/dashboards/firewall_insights.json
diff --git a/fast/stages/02-networking-peering/data/dashboards/vpn.json b/fast/stages/2-networking-a-peering/data/dashboards/vpn.json
similarity index 100%
rename from fast/stages/02-networking-peering/data/dashboards/vpn.json
rename to fast/stages/2-networking-a-peering/data/dashboards/vpn.json
diff --git a/fast/stages/02-networking-nva/data/firewall-rules/dev/rules.yaml b/fast/stages/2-networking-a-peering/data/firewall-rules/dev/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/firewall-rules/dev/rules.yaml
rename to fast/stages/2-networking-a-peering/data/firewall-rules/dev/rules.yaml
diff --git a/fast/stages/02-networking-peering/data/firewall-rules/landing/rules.yaml b/fast/stages/2-networking-a-peering/data/firewall-rules/landing/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/firewall-rules/landing/rules.yaml
rename to fast/stages/2-networking-a-peering/data/firewall-rules/landing/rules.yaml
diff --git a/fast/stages/02-networking-nva/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-a-peering/data/hierarchical-policy-rules.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/hierarchical-policy-rules.yaml
rename to fast/stages/2-networking-a-peering/data/hierarchical-policy-rules.yaml
diff --git a/fast/stages/02-networking-peering/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/subnets/dev/dev-dataplatform-ew1.yaml
rename to fast/stages/2-networking-a-peering/data/subnets/dev/dev-dataplatform-ew1.yaml
diff --git a/fast/stages/02-networking-peering/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/subnets/dev/dev-default-ew1.yaml
rename to fast/stages/2-networking-a-peering/data/subnets/dev/dev-default-ew1.yaml
diff --git a/fast/stages/02-networking-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml
rename to fast/stages/2-networking-a-peering/data/subnets/dev/dev-gke-nodes-ew1.yaml
diff --git a/fast/stages/02-networking-peering/data/subnets/landing/landing-default-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/landing/landing-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/subnets/landing/landing-default-ew1.yaml
rename to fast/stages/2-networking-a-peering/data/subnets/landing/landing-default-ew1.yaml
diff --git a/fast/stages/02-networking-peering/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/2-networking-a-peering/data/subnets/prod/prod-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/subnets/prod/prod-default-ew1.yaml
rename to fast/stages/2-networking-a-peering/data/subnets/prod/prod-default-ew1.yaml
diff --git a/fast/stages/02-networking-peering/diagram.png b/fast/stages/2-networking-a-peering/diagram.png
similarity index 100%
rename from fast/stages/02-networking-peering/diagram.png
rename to fast/stages/2-networking-a-peering/diagram.png
diff --git a/fast/stages/02-networking-peering/diagram.svg b/fast/stages/2-networking-a-peering/diagram.svg
similarity index 100%
rename from fast/stages/02-networking-peering/diagram.svg
rename to fast/stages/2-networking-a-peering/diagram.svg
diff --git a/fast/stages/02-networking-peering/dns-dev.tf b/fast/stages/2-networking-a-peering/dns-dev.tf
similarity index 100%
rename from fast/stages/02-networking-peering/dns-dev.tf
rename to fast/stages/2-networking-a-peering/dns-dev.tf
diff --git a/fast/stages/02-networking-peering/dns-landing.tf b/fast/stages/2-networking-a-peering/dns-landing.tf
similarity index 100%
rename from fast/stages/02-networking-peering/dns-landing.tf
rename to fast/stages/2-networking-a-peering/dns-landing.tf
diff --git a/fast/stages/02-networking-peering/dns-prod.tf b/fast/stages/2-networking-a-peering/dns-prod.tf
similarity index 100%
rename from fast/stages/02-networking-peering/dns-prod.tf
rename to fast/stages/2-networking-a-peering/dns-prod.tf
diff --git a/fast/stages/02-networking-peering/landing.tf b/fast/stages/2-networking-a-peering/landing.tf
similarity index 100%
rename from fast/stages/02-networking-peering/landing.tf
rename to fast/stages/2-networking-a-peering/landing.tf
diff --git a/fast/stages/02-networking-peering/main.tf b/fast/stages/2-networking-a-peering/main.tf
similarity index 100%
rename from fast/stages/02-networking-peering/main.tf
rename to fast/stages/2-networking-a-peering/main.tf
diff --git a/fast/stages/02-networking-nva/monitoring.tf b/fast/stages/2-networking-a-peering/monitoring.tf
similarity index 100%
rename from fast/stages/02-networking-nva/monitoring.tf
rename to fast/stages/2-networking-a-peering/monitoring.tf
diff --git a/fast/stages/02-networking-vpn/outputs.tf b/fast/stages/2-networking-a-peering/outputs.tf
similarity index 96%
rename from fast/stages/02-networking-vpn/outputs.tf
rename to fast/stages/2-networking-a-peering/outputs.tf
index 3b97b7f254..0e101f4123 100644
--- a/fast/stages/02-networking-vpn/outputs.tf
+++ b/fast/stages/2-networking-a-peering/outputs.tf
@@ -48,13 +48,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/02-networking.auto.tfvars.json"
+ filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/02-networking.auto.tfvars.json"
+ name = "tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/02-networking-peering/peerings.tf b/fast/stages/2-networking-a-peering/peerings.tf
similarity index 100%
rename from fast/stages/02-networking-peering/peerings.tf
rename to fast/stages/2-networking-a-peering/peerings.tf
diff --git a/fast/stages/02-networking-peering/spoke-dev.tf b/fast/stages/2-networking-a-peering/spoke-dev.tf
similarity index 100%
rename from fast/stages/02-networking-peering/spoke-dev.tf
rename to fast/stages/2-networking-a-peering/spoke-dev.tf
diff --git a/fast/stages/02-networking-peering/spoke-prod.tf b/fast/stages/2-networking-a-peering/spoke-prod.tf
similarity index 100%
rename from fast/stages/02-networking-peering/spoke-prod.tf
rename to fast/stages/2-networking-a-peering/spoke-prod.tf
diff --git a/fast/stages/02-networking-peering/test-resources.tf b/fast/stages/2-networking-a-peering/test-resources.tf
similarity index 100%
rename from fast/stages/02-networking-peering/test-resources.tf
rename to fast/stages/2-networking-a-peering/test-resources.tf
diff --git a/fast/stages/02-networking-peering/variables-peerings.tf b/fast/stages/2-networking-a-peering/variables-peerings.tf
similarity index 100%
rename from fast/stages/02-networking-peering/variables-peerings.tf
rename to fast/stages/2-networking-a-peering/variables-peerings.tf
diff --git a/fast/stages/02-networking-vpn/variables.tf b/fast/stages/2-networking-a-peering/variables.tf
similarity index 91%
rename from fast/stages/02-networking-vpn/variables.tf
rename to fast/stages/2-networking-a-peering/variables.tf
index faa9169877..fb2fd8745b 100644
--- a/fast/stages/02-networking-vpn/variables.tf
+++ b/fast/stages/2-networking-a-peering/variables.tf
@@ -15,7 +15,7 @@
*/
variable "automation" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Automation resources created by the bootstrap stage."
type = object({
outputs_bucket = string
@@ -23,12 +23,16 @@ variable "automation" {
}
variable "billing_account" {
- # tfdoc:variable:source 00-bootstrap
- description = "Billing account id and organization id ('nnnnnnnn' or null)."
+ # tfdoc:variable:source 0-bootstrap
+ description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false."
type = object({
- id = string
- organization_id = number
+ id = string
+ is_org_level = optional(bool, true)
})
+ validation {
+ condition = var.billing_account.is_org_level != null
+ error_message = "Invalid `null` value for `billing_account.is_org_level`."
+ }
}
variable "custom_adv" {
@@ -49,7 +53,7 @@ variable "custom_adv" {
}
variable "custom_roles" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Custom roles defined at the org level, in key => id format."
type = object({
service_project_network_admin = string
@@ -72,7 +76,7 @@ variable "dns" {
}
variable "folder_ids" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created."
type = object({
networking = string
@@ -100,7 +104,7 @@ variable "l7ilb_subnets" {
}
variable "organization" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Organization details."
type = object({
domain = string
@@ -116,7 +120,7 @@ variable "outputs_location" {
}
variable "prefix" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Prefix used for resources that need unique names. Use 9 characters or less."
type = string
@@ -191,7 +195,7 @@ variable "router_onprem_configs" {
}
variable "service_accounts" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Automation service accounts in name => email format."
type = object({
data-platform-dev = string
diff --git a/fast/stages/02-networking-peering/vpn-onprem.tf b/fast/stages/2-networking-a-peering/vpn-onprem.tf
similarity index 100%
rename from fast/stages/02-networking-peering/vpn-onprem.tf
rename to fast/stages/2-networking-a-peering/vpn-onprem.tf
diff --git a/fast/stages/02-networking-separate-envs/.gitignore b/fast/stages/2-networking-b-vpn/.gitignore
similarity index 100%
rename from fast/stages/02-networking-separate-envs/.gitignore
rename to fast/stages/2-networking-b-vpn/.gitignore
diff --git a/fast/stages/02-networking-separate-envs/IAM.md b/fast/stages/2-networking-b-vpn/IAM.md
similarity index 100%
rename from fast/stages/02-networking-separate-envs/IAM.md
rename to fast/stages/2-networking-b-vpn/IAM.md
diff --git a/fast/stages/02-networking-vpn/README.md b/fast/stages/2-networking-b-vpn/README.md
similarity index 94%
rename from fast/stages/02-networking-vpn/README.md
rename to fast/stages/2-networking-b-vpn/README.md
index 047a1189ce..8dbea716cc 100644
--- a/fast/stages/02-networking-vpn/README.md
+++ b/fast/stages/2-networking-b-vpn/README.md
@@ -45,10 +45,10 @@ This is a summary of the main options:
- [HA VPN](https://cloud.google.com/network-connectivity/docs/vpn/concepts/topologies) (implemented here)
- Pros: simple compatibility with GCP services that leverage peering internally, better control on routes, avoids peering groups shared quotas and limits
- Cons: additional cost, marginal increase in latency, requires multiple tunnels for full bandwidth
-- [VPC Peering](https://cloud.google.com/vpc/docs/vpc-peering) (implemented by [02-networking-peering](../02-networking-peering/))
+- [VPC Peering](https://cloud.google.com/vpc/docs/vpc-peering) (implemented by [02-networking-peering](../2-networking-a-peering/))
- Pros: no additional costs, full bandwidth with no configurations, no extra latency
- Cons: no transitivity (e.g. to GKE masters, Cloud SQL, etc.), no selective exchange of routes, several quotas and limits shared between VPCs in a peering group
-- [Multi-NIC appliances](https://cloud.google.com/architecture/best-practices-vpc-design#multi-nic) (implemented by [02-networking-nva](../02-networking-nva/))
+- [Multi-NIC appliances](https://cloud.google.com/architecture/best-practices-vpc-design#multi-nic) (implemented by [02-networking-nva](../2-networking-c-nva/))
- Pros: additional security features (e.g. IPS), potentially better integration with on-prem systems by using the same vendor
- Cons: complex HA/failover setup, limited by VM bandwidth and scale, additional costs for VMs and licenses, out of band management of a critical cloud component
@@ -128,7 +128,7 @@ This configuration is battle-tested, and flexible enough to lend itself to simpl
## How to run this stage
-This stage is meant to be executed after the [resman](../01-resman) stage has run, as it leverages the automation service account and bucket created there, and additional resources configured in the [bootstrap](../00-bootstrap) stage.
+This stage is meant to be executed after the [resman](../1-resman) stage has run, as it leverages the automation service account and bucket created there, and additional resources configured in the [bootstrap](../0-bootstrap) stage.
It's of course possible to run this stage in isolation, but that's outside the scope of this document, and you would need to refer to the code for the previous stages for the environmental requirements.
@@ -136,7 +136,7 @@ Before running this stage, you need to make sure you have the correct credential
### Providers configuration
-The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage during the [resource management](../01-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`).
+The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage during the [resource management](../1-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`).
To simplify setup, the previous stage pre-configures a valid providers file in its output, and optionally writes it to a local file if the `outputs_location` variable is set to a valid path.
@@ -150,7 +150,7 @@ ln -s ~/fast-config/providers/02-networking-providers.tf .
If you have not configured `outputs_location` in bootstrap, you can derive the providers file from that stage's outputs:
```bash
-cd ../01-resman
+cd ../1-resman
terraform output -json providers | jq -r '.["02-networking"]' \
> ../02-networking/providers.tf
```
@@ -320,23 +320,23 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 00-bootstrap
|
-| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…})
| ✓ | | 00-bootstrap
|
-| [folder_ids](variables.tf#L74) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…})
| ✓ | | 01-resman
|
-| [organization](variables.tf#L102) | Organization details. | object({…})
| ✓ | | 00-bootstrap
|
-| [prefix](variables.tf#L118) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 00-bootstrap
|
-| [custom_adv](variables.tf#L34) | Custom advertisement definitions in name => range format. | map(string)
| | {…}
| |
-| [custom_roles](variables.tf#L51) | Custom roles defined at the org level, in key => id format. | object({…})
| | null
| 00-bootstrap
|
-| [data_dir](variables.tf#L60) | Relative path for the folder storing configuration data for network resources. | string
| | "data"
| |
-| [dns](variables.tf#L66) | Onprem DNS resolvers. | map(list(string))
| | {…}
| |
-| [l7ilb_subnets](variables.tf#L84) | Subnets used for L7 ILBs. | map(list(object({…})))
| | {…}
| |
-| [outputs_location](variables.tf#L112) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
-| [psa_ranges](variables.tf#L129) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…})
| | null
| |
-| [region_trigram](variables.tf#L166) | Short names for GCP regions. | map(string)
| | {…}
| |
-| [router_onprem_configs](variables.tf#L175) | Configurations for routers used for onprem connectivity. | map(object({…}))
| | {…}
| |
+| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 0-bootstrap
|
+| [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…})
| ✓ | | 0-bootstrap
|
+| [folder_ids](variables.tf#L78) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…})
| ✓ | | 1-resman
|
+| [organization](variables.tf#L106) | Organization details. | object({…})
| ✓ | | 0-bootstrap
|
+| [prefix](variables.tf#L122) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 0-bootstrap
|
+| [custom_adv](variables.tf#L38) | Custom advertisement definitions in name => range format. | map(string)
| | {…}
| |
+| [custom_roles](variables.tf#L55) | Custom roles defined at the org level, in key => id format. | object({…})
| | null
| 0-bootstrap
|
+| [data_dir](variables.tf#L64) | Relative path for the folder storing configuration data for network resources. | string
| | "data"
| |
+| [dns](variables.tf#L70) | Onprem DNS resolvers. | map(list(string))
| | {…}
| |
+| [l7ilb_subnets](variables.tf#L88) | Subnets used for L7 ILBs. | map(list(object({…})))
| | {…}
| |
+| [outputs_location](variables.tf#L116) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
+| [psa_ranges](variables.tf#L133) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…})
| | null
| |
+| [region_trigram](variables.tf#L170) | Short names for GCP regions. | map(string)
| | {…}
| |
+| [router_onprem_configs](variables.tf#L179) | Configurations for routers used for onprem connectivity. | map(object({…}))
| | {…}
| |
| [router_spoke_configs](variables-vpn.tf#L18) | Configurations for routers used for internal connectivity. | map(object({…}))
| | {…}
| |
-| [service_accounts](variables.tf#L193) | Automation service accounts in name => email format. | object({…})
| | null
| 01-resman
|
-| [vpn_onprem_configs](variables.tf#L207) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
+| [service_accounts](variables.tf#L197) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
+| [vpn_onprem_configs](variables.tf#L211) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
| [vpn_spoke_configs](variables-vpn.tf#L37) | VPN gateway configuration for spokes. | map(object({…}))
| | {…}
| |
## Outputs
diff --git a/fast/stages/02-networking-peering/data/cidrs.yaml b/fast/stages/2-networking-b-vpn/data/cidrs.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/cidrs.yaml
rename to fast/stages/2-networking-b-vpn/data/cidrs.yaml
diff --git a/fast/stages/02-networking-separate-envs/data/dashboards/firewall_insights.json b/fast/stages/2-networking-b-vpn/data/dashboards/firewall_insights.json
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/dashboards/firewall_insights.json
rename to fast/stages/2-networking-b-vpn/data/dashboards/firewall_insights.json
diff --git a/fast/stages/02-networking-separate-envs/data/dashboards/vpn.json b/fast/stages/2-networking-b-vpn/data/dashboards/vpn.json
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/dashboards/vpn.json
rename to fast/stages/2-networking-b-vpn/data/dashboards/vpn.json
diff --git a/fast/stages/02-networking-peering/data/firewall-rules/dev/rules.yaml b/fast/stages/2-networking-b-vpn/data/firewall-rules/dev/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/firewall-rules/dev/rules.yaml
rename to fast/stages/2-networking-b-vpn/data/firewall-rules/dev/rules.yaml
diff --git a/fast/stages/02-networking-vpn/data/firewall-rules/landing/rules.yaml b/fast/stages/2-networking-b-vpn/data/firewall-rules/landing/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/firewall-rules/landing/rules.yaml
rename to fast/stages/2-networking-b-vpn/data/firewall-rules/landing/rules.yaml
diff --git a/fast/stages/02-networking-peering/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-b-vpn/data/hierarchical-policy-rules.yaml
similarity index 100%
rename from fast/stages/02-networking-peering/data/hierarchical-policy-rules.yaml
rename to fast/stages/2-networking-b-vpn/data/hierarchical-policy-rules.yaml
diff --git a/fast/stages/02-networking-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml
rename to fast/stages/2-networking-b-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml
diff --git a/fast/stages/02-networking-separate-envs/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/subnets/dev/dev-default-ew1.yaml
rename to fast/stages/2-networking-b-vpn/data/subnets/dev/dev-default-ew1.yaml
diff --git a/fast/stages/02-networking-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml
rename to fast/stages/2-networking-b-vpn/data/subnets/dev/dev-gke-nodes-ew1.yaml
diff --git a/fast/stages/02-networking-vpn/data/subnets/landing/landing-default-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/landing/landing-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/subnets/landing/landing-default-ew1.yaml
rename to fast/stages/2-networking-b-vpn/data/subnets/landing/landing-default-ew1.yaml
diff --git a/fast/stages/02-networking-separate-envs/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/2-networking-b-vpn/data/subnets/prod/prod-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/subnets/prod/prod-default-ew1.yaml
rename to fast/stages/2-networking-b-vpn/data/subnets/prod/prod-default-ew1.yaml
diff --git a/fast/stages/02-networking-vpn/diagram.png b/fast/stages/2-networking-b-vpn/diagram.png
similarity index 100%
rename from fast/stages/02-networking-vpn/diagram.png
rename to fast/stages/2-networking-b-vpn/diagram.png
diff --git a/fast/stages/02-networking-vpn/diagram.svg b/fast/stages/2-networking-b-vpn/diagram.svg
similarity index 100%
rename from fast/stages/02-networking-vpn/diagram.svg
rename to fast/stages/2-networking-b-vpn/diagram.svg
diff --git a/fast/stages/02-networking-vpn/dns-dev.tf b/fast/stages/2-networking-b-vpn/dns-dev.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/dns-dev.tf
rename to fast/stages/2-networking-b-vpn/dns-dev.tf
diff --git a/fast/stages/02-networking-vpn/dns-landing.tf b/fast/stages/2-networking-b-vpn/dns-landing.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/dns-landing.tf
rename to fast/stages/2-networking-b-vpn/dns-landing.tf
diff --git a/fast/stages/02-networking-vpn/dns-prod.tf b/fast/stages/2-networking-b-vpn/dns-prod.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/dns-prod.tf
rename to fast/stages/2-networking-b-vpn/dns-prod.tf
diff --git a/fast/stages/02-networking-vpn/landing.tf b/fast/stages/2-networking-b-vpn/landing.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/landing.tf
rename to fast/stages/2-networking-b-vpn/landing.tf
diff --git a/fast/stages/02-networking-vpn/main.tf b/fast/stages/2-networking-b-vpn/main.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/main.tf
rename to fast/stages/2-networking-b-vpn/main.tf
diff --git a/fast/stages/02-networking-peering/monitoring.tf b/fast/stages/2-networking-b-vpn/monitoring.tf
similarity index 100%
rename from fast/stages/02-networking-peering/monitoring.tf
rename to fast/stages/2-networking-b-vpn/monitoring.tf
diff --git a/fast/stages/02-networking-peering/outputs.tf b/fast/stages/2-networking-b-vpn/outputs.tf
similarity index 96%
rename from fast/stages/02-networking-peering/outputs.tf
rename to fast/stages/2-networking-b-vpn/outputs.tf
index 3b97b7f254..0e101f4123 100644
--- a/fast/stages/02-networking-peering/outputs.tf
+++ b/fast/stages/2-networking-b-vpn/outputs.tf
@@ -48,13 +48,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/02-networking.auto.tfvars.json"
+ filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/02-networking.auto.tfvars.json"
+ name = "tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/02-networking-vpn/spoke-dev.tf b/fast/stages/2-networking-b-vpn/spoke-dev.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/spoke-dev.tf
rename to fast/stages/2-networking-b-vpn/spoke-dev.tf
diff --git a/fast/stages/02-networking-vpn/spoke-prod.tf b/fast/stages/2-networking-b-vpn/spoke-prod.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/spoke-prod.tf
rename to fast/stages/2-networking-b-vpn/spoke-prod.tf
diff --git a/fast/stages/02-networking-vpn/test-resources.tf b/fast/stages/2-networking-b-vpn/test-resources.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/test-resources.tf
rename to fast/stages/2-networking-b-vpn/test-resources.tf
diff --git a/fast/stages/02-networking-vpn/variables-vpn.tf b/fast/stages/2-networking-b-vpn/variables-vpn.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/variables-vpn.tf
rename to fast/stages/2-networking-b-vpn/variables-vpn.tf
diff --git a/fast/stages/02-networking-peering/variables.tf b/fast/stages/2-networking-b-vpn/variables.tf
similarity index 91%
rename from fast/stages/02-networking-peering/variables.tf
rename to fast/stages/2-networking-b-vpn/variables.tf
index faa9169877..fb2fd8745b 100644
--- a/fast/stages/02-networking-peering/variables.tf
+++ b/fast/stages/2-networking-b-vpn/variables.tf
@@ -15,7 +15,7 @@
*/
variable "automation" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Automation resources created by the bootstrap stage."
type = object({
outputs_bucket = string
@@ -23,12 +23,16 @@ variable "automation" {
}
variable "billing_account" {
- # tfdoc:variable:source 00-bootstrap
- description = "Billing account id and organization id ('nnnnnnnn' or null)."
+ # tfdoc:variable:source 0-bootstrap
+ description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false."
type = object({
- id = string
- organization_id = number
+ id = string
+ is_org_level = optional(bool, true)
})
+ validation {
+ condition = var.billing_account.is_org_level != null
+ error_message = "Invalid `null` value for `billing_account.is_org_level`."
+ }
}
variable "custom_adv" {
@@ -49,7 +53,7 @@ variable "custom_adv" {
}
variable "custom_roles" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Custom roles defined at the org level, in key => id format."
type = object({
service_project_network_admin = string
@@ -72,7 +76,7 @@ variable "dns" {
}
variable "folder_ids" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created."
type = object({
networking = string
@@ -100,7 +104,7 @@ variable "l7ilb_subnets" {
}
variable "organization" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Organization details."
type = object({
domain = string
@@ -116,7 +120,7 @@ variable "outputs_location" {
}
variable "prefix" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Prefix used for resources that need unique names. Use 9 characters or less."
type = string
@@ -191,7 +195,7 @@ variable "router_onprem_configs" {
}
variable "service_accounts" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Automation service accounts in name => email format."
type = object({
data-platform-dev = string
diff --git a/fast/stages/02-networking-vpn/vpn-onprem.tf b/fast/stages/2-networking-b-vpn/vpn-onprem.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/vpn-onprem.tf
rename to fast/stages/2-networking-b-vpn/vpn-onprem.tf
diff --git a/fast/stages/02-networking-vpn/vpn-spoke-dev.tf b/fast/stages/2-networking-b-vpn/vpn-spoke-dev.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/vpn-spoke-dev.tf
rename to fast/stages/2-networking-b-vpn/vpn-spoke-dev.tf
diff --git a/fast/stages/02-networking-vpn/vpn-spoke-prod-ew1.tf b/fast/stages/2-networking-b-vpn/vpn-spoke-prod-ew1.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/vpn-spoke-prod-ew1.tf
rename to fast/stages/2-networking-b-vpn/vpn-spoke-prod-ew1.tf
diff --git a/fast/stages/02-networking-vpn/vpn-spoke-prod-ew4.tf b/fast/stages/2-networking-b-vpn/vpn-spoke-prod-ew4.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/vpn-spoke-prod-ew4.tf
rename to fast/stages/2-networking-b-vpn/vpn-spoke-prod-ew4.tf
diff --git a/fast/stages/02-networking-nva/README.md b/fast/stages/2-networking-c-nva/README.md
similarity index 94%
rename from fast/stages/02-networking-nva/README.md
rename to fast/stages/2-networking-c-nva/README.md
index 79c27e8de6..260d320f24 100644
--- a/fast/stages/02-networking-nva/README.md
+++ b/fast/stages/2-networking-c-nva/README.md
@@ -69,7 +69,7 @@ Internal connectivity (e.g. between the trusted landing VPC and the spokes) is r
This is an options summary:
-- [VPC Peering](https://cloud.google.com/vpc/docs/vpc-peering) (used here to connect the trusted landing VPC with the spokes, also used by [02-networking-vpn](../02-networking-vpn/))
+- [VPC Peering](https://cloud.google.com/vpc/docs/vpc-peering) (used here to connect the trusted landing VPC with the spokes, also used by [02-networking-vpn](../2-networking-b-vpn/))
- Pros: no additional costs, full bandwidth with no configurations, no extra latency
- Cons: no transitivity (e.g. to GKE masters, Cloud SQL, etc.), no selective exchange of routes, several quotas and limits shared between VPCs in a peering group
- [Multi-NIC appliances](https://cloud.google.com/architecture/best-practices-vpc-design#multi-nic) (used here to connect the trusted landing and untrusted VPCs)
@@ -192,7 +192,7 @@ This configuration is battle-tested, and flexible enough to lend itself to simpl
## How to run this stage
-This stage is meant to be executed after the [resman](../01-resman) stage has run. It leverages the automation service account and the storage bucket created there, and additional resources configured in the [bootstrap](../00-bootstrap) stage.
+This stage is meant to be executed after the [resman](../1-resman) stage has run. It leverages the automation service account and the storage bucket created there, and additional resources configured in the [bootstrap](../0-bootstrap) stage.
It's possible to run this stage in isolation, but that's outside of the scope of this document. Please, refer to the previous stages for the environment requirements.
@@ -200,7 +200,7 @@ Before running this stage, you need to make sure you have the correct credential
### Providers configuration
-The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage, during the [resource management](../01-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`).
+The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage, during the [resource management](../1-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`).
To simplify the setup, the previous stage pre-configures a valid providers file in its output and optionally writes it to a local file if the `outputs_location` variable is set to a valid path.
@@ -214,9 +214,9 @@ ln -s ~/fast-config/providers/02-networking-providers.tf .
If you have not configured `outputs_location` in bootstrap, you can derive the providers file from that stage outputs:
```bash
-cd ../01-resman
+cd ../1-resman
terraform output -json providers | jq -r '.["02-networking"]' \
- > ../02-networking-nva/providers.tf
+ > ../2-networking-c-nva/providers.tf
```
### Variable configuration
@@ -371,23 +371,23 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 00-bootstrap
|
-| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…})
| ✓ | | 00-bootstrap
|
-| [folder_ids](variables.tf#L79) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…})
| ✓ | | 01-resman
|
-| [organization](variables.tf#L115) | Organization details. | object({…})
| ✓ | | 00-bootstrap
|
-| [prefix](variables.tf#L131) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 00-bootstrap
|
-| [custom_adv](variables.tf#L34) | Custom advertisement definitions in name => range format. | map(string)
| | {…}
| |
-| [custom_roles](variables.tf#L56) | Custom roles defined at the org level, in key => id format. | object({…})
| | null
| 00-bootstrap
|
-| [data_dir](variables.tf#L65) | Relative path for the folder storing configuration data for network resources. | string
| | "data"
| |
-| [dns](variables.tf#L71) | Onprem DNS resolvers. | map(list(string))
| | {…}
| |
-| [l7ilb_subnets](variables.tf#L89) | Subnets used for L7 ILBs. | map(list(object({…})))
| | {…}
| |
-| [onprem_cidr](variables.tf#L107) | Onprem addresses in name => range format. | map(string)
| | {…}
| |
-| [outputs_location](variables.tf#L125) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
-| [psa_ranges](variables.tf#L142) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…})
| | null
| |
-| [region_trigram](variables.tf#L183) | Short names for GCP regions. | map(string)
| | {…}
| |
-| [router_configs](variables.tf#L192) | Configurations for CRs and onprem routers. | map(object({…}))
| | {…}
| |
-| [service_accounts](variables.tf#L215) | Automation service accounts in name => email format. | object({…})
| | null
| 01-resman
|
-| [vpn_onprem_configs](variables.tf#L229) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
+| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 0-bootstrap
|
+| [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…})
| ✓ | | 0-bootstrap
|
+| [folder_ids](variables.tf#L83) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…})
| ✓ | | 1-resman
|
+| [organization](variables.tf#L119) | Organization details. | object({…})
| ✓ | | 0-bootstrap
|
+| [prefix](variables.tf#L135) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 0-bootstrap
|
+| [custom_adv](variables.tf#L38) | Custom advertisement definitions in name => range format. | map(string)
| | {…}
| |
+| [custom_roles](variables.tf#L60) | Custom roles defined at the org level, in key => id format. | object({…})
| | null
| 0-bootstrap
|
+| [data_dir](variables.tf#L69) | Relative path for the folder storing configuration data for network resources. | string
| | "data"
| |
+| [dns](variables.tf#L75) | Onprem DNS resolvers. | map(list(string))
| | {…}
| |
+| [l7ilb_subnets](variables.tf#L93) | Subnets used for L7 ILBs. | map(list(object({…})))
| | {…}
| |
+| [onprem_cidr](variables.tf#L111) | Onprem addresses in name => range format. | map(string)
| | {…}
| |
+| [outputs_location](variables.tf#L129) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
+| [psa_ranges](variables.tf#L146) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…})
| | null
| |
+| [region_trigram](variables.tf#L187) | Short names for GCP regions. | map(string)
| | {…}
| |
+| [router_configs](variables.tf#L196) | Configurations for CRs and onprem routers. | map(object({…}))
| | {…}
| |
+| [service_accounts](variables.tf#L219) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
+| [vpn_onprem_configs](variables.tf#L233) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
## Outputs
diff --git a/fast/stages/02-networking-separate-envs/data/cidrs.yaml b/fast/stages/2-networking-c-nva/data/cidrs.yaml
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/cidrs.yaml
rename to fast/stages/2-networking-c-nva/data/cidrs.yaml
diff --git a/fast/stages/02-networking-nva/data/dashboards/firewall_insights.json b/fast/stages/2-networking-c-nva/data/dashboards/firewall_insights.json
similarity index 100%
rename from fast/stages/02-networking-nva/data/dashboards/firewall_insights.json
rename to fast/stages/2-networking-c-nva/data/dashboards/firewall_insights.json
diff --git a/fast/stages/02-networking-nva/data/dashboards/vpn.json b/fast/stages/2-networking-c-nva/data/dashboards/vpn.json
similarity index 100%
rename from fast/stages/02-networking-nva/data/dashboards/vpn.json
rename to fast/stages/2-networking-c-nva/data/dashboards/vpn.json
diff --git a/fast/stages/02-networking-vpn/data/firewall-rules/dev/rules.yaml b/fast/stages/2-networking-c-nva/data/firewall-rules/dev/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/firewall-rules/dev/rules.yaml
rename to fast/stages/2-networking-c-nva/data/firewall-rules/dev/rules.yaml
diff --git a/fast/stages/02-networking-nva/data/firewall-rules/landing-trusted/rules.yaml b/fast/stages/2-networking-c-nva/data/firewall-rules/landing-trusted/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/firewall-rules/landing-trusted/rules.yaml
rename to fast/stages/2-networking-c-nva/data/firewall-rules/landing-trusted/rules.yaml
diff --git a/fast/stages/02-networking-nva/data/firewall-rules/landing-untrusted/rules.yaml b/fast/stages/2-networking-c-nva/data/firewall-rules/landing-untrusted/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/firewall-rules/landing-untrusted/rules.yaml
rename to fast/stages/2-networking-c-nva/data/firewall-rules/landing-untrusted/rules.yaml
diff --git a/fast/stages/02-networking-separate-envs/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-c-nva/data/hierarchical-policy-rules.yaml
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/hierarchical-policy-rules.yaml
rename to fast/stages/2-networking-c-nva/data/hierarchical-policy-rules.yaml
diff --git a/fast/stages/02-networking-nva/data/nva-startup-script.tftpl b/fast/stages/2-networking-c-nva/data/nva-startup-script.tftpl
similarity index 100%
rename from fast/stages/02-networking-nva/data/nva-startup-script.tftpl
rename to fast/stages/2-networking-c-nva/data/nva-startup-script.tftpl
diff --git a/fast/stages/02-networking-nva/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/dev/dev-dataplatform-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/dev/dev-dataplatform-ew1.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/dev/dev-dataplatform-ew1.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/dev/dev-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew1.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/dev/dev-default-ew1.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew4.yaml b/fast/stages/2-networking-c-nva/data/subnets/dev/dev-default-ew4.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew4.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/dev/dev-default-ew4.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml b/fast/stages/2-networking-c-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml b/fast/stages/2-networking-c-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/2-networking-c-nva/data/subnets/prod/prod-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew1.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/prod/prod-default-ew1.yaml
diff --git a/fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew4.yaml b/fast/stages/2-networking-c-nva/data/subnets/prod/prod-default-ew4.yaml
similarity index 100%
rename from fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew4.yaml
rename to fast/stages/2-networking-c-nva/data/subnets/prod/prod-default-ew4.yaml
diff --git a/fast/stages/02-networking-nva/diagram.png b/fast/stages/2-networking-c-nva/diagram.png
similarity index 100%
rename from fast/stages/02-networking-nva/diagram.png
rename to fast/stages/2-networking-c-nva/diagram.png
diff --git a/fast/stages/02-networking-nva/diagram.svg b/fast/stages/2-networking-c-nva/diagram.svg
similarity index 100%
rename from fast/stages/02-networking-nva/diagram.svg
rename to fast/stages/2-networking-c-nva/diagram.svg
diff --git a/fast/stages/02-networking-nva/dns-dev.tf b/fast/stages/2-networking-c-nva/dns-dev.tf
similarity index 100%
rename from fast/stages/02-networking-nva/dns-dev.tf
rename to fast/stages/2-networking-c-nva/dns-dev.tf
diff --git a/fast/stages/02-networking-nva/dns-landing.tf b/fast/stages/2-networking-c-nva/dns-landing.tf
similarity index 100%
rename from fast/stages/02-networking-nva/dns-landing.tf
rename to fast/stages/2-networking-c-nva/dns-landing.tf
diff --git a/fast/stages/02-networking-nva/dns-prod.tf b/fast/stages/2-networking-c-nva/dns-prod.tf
similarity index 100%
rename from fast/stages/02-networking-nva/dns-prod.tf
rename to fast/stages/2-networking-c-nva/dns-prod.tf
diff --git a/fast/stages/02-networking-nva/landing.tf b/fast/stages/2-networking-c-nva/landing.tf
similarity index 100%
rename from fast/stages/02-networking-nva/landing.tf
rename to fast/stages/2-networking-c-nva/landing.tf
diff --git a/fast/stages/02-networking-nva/main.tf b/fast/stages/2-networking-c-nva/main.tf
similarity index 100%
rename from fast/stages/02-networking-nva/main.tf
rename to fast/stages/2-networking-c-nva/main.tf
diff --git a/fast/stages/02-networking-vpn/monitoring.tf b/fast/stages/2-networking-c-nva/monitoring.tf
similarity index 100%
rename from fast/stages/02-networking-vpn/monitoring.tf
rename to fast/stages/2-networking-c-nva/monitoring.tf
diff --git a/fast/stages/02-networking-nva/nva.tf b/fast/stages/2-networking-c-nva/nva.tf
similarity index 100%
rename from fast/stages/02-networking-nva/nva.tf
rename to fast/stages/2-networking-c-nva/nva.tf
diff --git a/fast/stages/02-networking-nva/outputs.tf b/fast/stages/2-networking-c-nva/outputs.tf
similarity index 96%
rename from fast/stages/02-networking-nva/outputs.tf
rename to fast/stages/2-networking-c-nva/outputs.tf
index df324570dd..97db168ee2 100644
--- a/fast/stages/02-networking-nva/outputs.tf
+++ b/fast/stages/2-networking-c-nva/outputs.tf
@@ -43,13 +43,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/02-networking.auto.tfvars.json"
+ filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/02-networking.auto.tfvars.json"
+ name = "tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/02-networking-nva/spoke-dev.tf b/fast/stages/2-networking-c-nva/spoke-dev.tf
similarity index 100%
rename from fast/stages/02-networking-nva/spoke-dev.tf
rename to fast/stages/2-networking-c-nva/spoke-dev.tf
diff --git a/fast/stages/02-networking-nva/spoke-prod.tf b/fast/stages/2-networking-c-nva/spoke-prod.tf
similarity index 100%
rename from fast/stages/02-networking-nva/spoke-prod.tf
rename to fast/stages/2-networking-c-nva/spoke-prod.tf
diff --git a/fast/stages/02-networking-nva/test-resources.tf b/fast/stages/2-networking-c-nva/test-resources.tf
similarity index 100%
rename from fast/stages/02-networking-nva/test-resources.tf
rename to fast/stages/2-networking-c-nva/test-resources.tf
diff --git a/fast/stages/02-networking-nva/variables.tf b/fast/stages/2-networking-c-nva/variables.tf
similarity index 93%
rename from fast/stages/02-networking-nva/variables.tf
rename to fast/stages/2-networking-c-nva/variables.tf
index ea6c0bf0d7..c6516314ba 100644
--- a/fast/stages/02-networking-nva/variables.tf
+++ b/fast/stages/2-networking-c-nva/variables.tf
@@ -15,7 +15,7 @@
*/
variable "automation" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Automation resources created by the bootstrap stage."
type = object({
outputs_bucket = string
@@ -23,12 +23,16 @@ variable "automation" {
}
variable "billing_account" {
- # tfdoc:variable:source 00-bootstrap
- description = "Billing account id and organization id ('nnnnnnnn' or null)."
+ # tfdoc:variable:source 0-bootstrap
+ description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false."
type = object({
- id = string
- organization_id = number
+ id = string
+ is_org_level = optional(bool, true)
})
+ validation {
+ condition = var.billing_account.is_org_level != null
+ error_message = "Invalid `null` value for `billing_account.is_org_level`."
+ }
}
variable "custom_adv" {
@@ -54,7 +58,7 @@ variable "custom_adv" {
}
variable "custom_roles" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Custom roles defined at the org level, in key => id format."
type = object({
service_project_network_admin = string
@@ -77,7 +81,7 @@ variable "dns" {
}
variable "folder_ids" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created."
type = object({
networking = string
@@ -113,7 +117,7 @@ variable "onprem_cidr" {
}
variable "organization" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Organization details."
type = object({
domain = string
@@ -129,7 +133,7 @@ variable "outputs_location" {
}
variable "prefix" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Prefix used for resources that need unique names. Use 9 characters or less."
type = string
@@ -213,7 +217,7 @@ variable "router_configs" {
}
variable "service_accounts" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Automation service accounts in name => email format."
type = object({
data-platform-dev = string
diff --git a/fast/stages/02-networking-nva/vpn-onprem.tf b/fast/stages/2-networking-c-nva/vpn-onprem.tf
similarity index 100%
rename from fast/stages/02-networking-nva/vpn-onprem.tf
rename to fast/stages/2-networking-c-nva/vpn-onprem.tf
diff --git a/fast/stages/02-networking-vpn/.gitignore b/fast/stages/2-networking-d-separate-envs/.gitignore
similarity index 100%
rename from fast/stages/02-networking-vpn/.gitignore
rename to fast/stages/2-networking-d-separate-envs/.gitignore
diff --git a/fast/stages/02-networking-vpn/IAM.md b/fast/stages/2-networking-d-separate-envs/IAM.md
similarity index 100%
rename from fast/stages/02-networking-vpn/IAM.md
rename to fast/stages/2-networking-d-separate-envs/IAM.md
diff --git a/fast/stages/02-networking-separate-envs/README.md b/fast/stages/2-networking-d-separate-envs/README.md
similarity index 92%
rename from fast/stages/02-networking-separate-envs/README.md
rename to fast/stages/2-networking-d-separate-envs/README.md
index 66b31646ec..ad36ca3a1b 100644
--- a/fast/stages/02-networking-separate-envs/README.md
+++ b/fast/stages/2-networking-d-separate-envs/README.md
@@ -89,7 +89,7 @@ This configuration is battle-tested, and flexible enough to lend itself to simpl
## How to run this stage
-This stage is meant to be executed after the [resman](../01-resman) stage has run, as it leverages the automation service account and bucket created there, and additional resources configured in the [bootstrap](../00-bootstrap) stage.
+This stage is meant to be executed after the [resman](../1-resman) stage has run, as it leverages the automation service account and bucket created there, and additional resources configured in the [bootstrap](../0-bootstrap) stage.
It's of course possible to run this stage in isolation, but that's outside the scope of this document, and you would need to refer to the code for the previous stages for the environmental requirements.
@@ -97,7 +97,7 @@ Before running this stage, you need to make sure you have the correct credential
### Providers configuration
-The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage during the [resource management](../01-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`).
+The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage during the [resource management](../1-resman) stage, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`).
To simplify setup, the previous stage pre-configures a valid providers file in its output, and optionally writes it to a local file if the `outputs_location` variable is set to a valid path.
@@ -111,7 +111,7 @@ ln -s ~/fast-config/providers/02-networking-providers.tf .
If you have not configured `outputs_location` in bootstrap, you can derive the providers file from that stage's outputs:
```bash
-cd ../01-resman
+cd ../1-resman
terraform output -json providers | jq -r '.["02-networking"]' \
> ../02-networking/providers.tf
```
@@ -132,7 +132,7 @@ If you have set a valid value for `outputs_location` in the bootstrap and in the
ln -s ../../configs/example/02-networking/terraform-bootstrap.auto.tfvars.json
ln -s ../../configs/example/02-networking/terraform-resman.auto.tfvars.json
# also copy the tfvars file used for the bootstrap stage
-cp ../00-bootstrap/terraform.tfvars .
+cp ../0-bootstrap/terraform.tfvars .
```
A second set of variables is specific to this stage, they are all optional so if you need to customize them, add them to the file copied from bootstrap.
@@ -238,21 +238,21 @@ You're now ready to run `terraform init` and `apply`.
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 00-bootstrap
|
-| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…})
| ✓ | | 00-bootstrap
|
-| [folder_ids](variables.tf#L74) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…})
| ✓ | | 01-resman
|
-| [organization](variables.tf#L102) | Organization details. | object({…})
| ✓ | | 00-bootstrap
|
-| [prefix](variables.tf#L118) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 00-bootstrap
|
-| [custom_adv](variables.tf#L34) | Custom advertisement definitions in name => range format. | map(string)
| | {…}
| |
-| [custom_roles](variables.tf#L50) | Custom roles defined at the org level, in key => id format. | object({…})
| | null
| 00-bootstrap
|
-| [data_dir](variables.tf#L59) | Relative path for the folder storing configuration data for network resources. | string
| | "data"
| |
-| [dns](variables.tf#L65) | Onprem DNS resolvers. | map(list(string))
| | {…}
| |
-| [l7ilb_subnets](variables.tf#L84) | Subnets used for L7 ILBs. | map(list(object({…})))
| | {…}
| |
-| [outputs_location](variables.tf#L112) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
-| [psa_ranges](variables.tf#L129) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…})
| | null
| |
-| [router_onprem_configs](variables.tf#L166) | Configurations for routers used for onprem connectivity. | map(object({…}))
| | {…}
| |
-| [service_accounts](variables.tf#L189) | Automation service accounts in name => email format. | object({…})
| | null
| 01-resman
|
-| [vpn_onprem_configs](variables.tf#L201) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
+| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 0-bootstrap
|
+| [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…})
| ✓ | | 0-bootstrap
|
+| [folder_ids](variables.tf#L78) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…})
| ✓ | | 1-resman
|
+| [organization](variables.tf#L106) | Organization details. | object({…})
| ✓ | | 0-bootstrap
|
+| [prefix](variables.tf#L122) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 0-bootstrap
|
+| [custom_adv](variables.tf#L38) | Custom advertisement definitions in name => range format. | map(string)
| | {…}
| |
+| [custom_roles](variables.tf#L54) | Custom roles defined at the org level, in key => id format. | object({…})
| | null
| 0-bootstrap
|
+| [data_dir](variables.tf#L63) | Relative path for the folder storing configuration data for network resources. | string
| | "data"
| |
+| [dns](variables.tf#L69) | Onprem DNS resolvers. | map(list(string))
| | {…}
| |
+| [l7ilb_subnets](variables.tf#L88) | Subnets used for L7 ILBs. | map(list(object({…})))
| | {…}
| |
+| [outputs_location](variables.tf#L116) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
+| [psa_ranges](variables.tf#L133) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…})
| | null
| |
+| [router_onprem_configs](variables.tf#L170) | Configurations for routers used for onprem connectivity. | map(object({…}))
| | {…}
| |
+| [service_accounts](variables.tf#L193) | Automation service accounts in name => email format. | object({…})
| | null
| 1-resman
|
+| [vpn_onprem_configs](variables.tf#L205) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
## Outputs
diff --git a/fast/stages/02-networking-vpn/data/cidrs.yaml b/fast/stages/2-networking-d-separate-envs/data/cidrs.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/cidrs.yaml
rename to fast/stages/2-networking-d-separate-envs/data/cidrs.yaml
diff --git a/fast/stages/02-networking-vpn/data/dashboards/firewall_insights.json b/fast/stages/2-networking-d-separate-envs/data/dashboards/firewall_insights.json
similarity index 100%
rename from fast/stages/02-networking-vpn/data/dashboards/firewall_insights.json
rename to fast/stages/2-networking-d-separate-envs/data/dashboards/firewall_insights.json
diff --git a/fast/stages/02-networking-vpn/data/dashboards/vpn.json b/fast/stages/2-networking-d-separate-envs/data/dashboards/vpn.json
similarity index 100%
rename from fast/stages/02-networking-vpn/data/dashboards/vpn.json
rename to fast/stages/2-networking-d-separate-envs/data/dashboards/vpn.json
diff --git a/fast/stages/02-networking-separate-envs/data/firewall-rules/dev/rules.yaml b/fast/stages/2-networking-d-separate-envs/data/firewall-rules/dev/rules.yaml
similarity index 100%
rename from fast/stages/02-networking-separate-envs/data/firewall-rules/dev/rules.yaml
rename to fast/stages/2-networking-d-separate-envs/data/firewall-rules/dev/rules.yaml
diff --git a/fast/stages/02-networking-vpn/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-d-separate-envs/data/hierarchical-policy-rules.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/hierarchical-policy-rules.yaml
rename to fast/stages/2-networking-d-separate-envs/data/hierarchical-policy-rules.yaml
diff --git a/fast/stages/02-networking-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml b/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/subnets/dev/dev-dataplatform-ew1.yaml
rename to fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-dataplatform-ew1.yaml
diff --git a/fast/stages/02-networking-vpn/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/subnets/dev/dev-default-ew1.yaml
rename to fast/stages/2-networking-d-separate-envs/data/subnets/dev/dev-default-ew1.yaml
diff --git a/fast/stages/02-networking-vpn/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/2-networking-d-separate-envs/data/subnets/prod/prod-default-ew1.yaml
similarity index 100%
rename from fast/stages/02-networking-vpn/data/subnets/prod/prod-default-ew1.yaml
rename to fast/stages/2-networking-d-separate-envs/data/subnets/prod/prod-default-ew1.yaml
diff --git a/fast/stages/02-networking-separate-envs/diagram.png b/fast/stages/2-networking-d-separate-envs/diagram.png
similarity index 100%
rename from fast/stages/02-networking-separate-envs/diagram.png
rename to fast/stages/2-networking-d-separate-envs/diagram.png
diff --git a/fast/stages/02-networking-separate-envs/diagram.svg b/fast/stages/2-networking-d-separate-envs/diagram.svg
similarity index 100%
rename from fast/stages/02-networking-separate-envs/diagram.svg
rename to fast/stages/2-networking-d-separate-envs/diagram.svg
diff --git a/fast/stages/02-networking-separate-envs/dns-dev.tf b/fast/stages/2-networking-d-separate-envs/dns-dev.tf
similarity index 100%
rename from fast/stages/02-networking-separate-envs/dns-dev.tf
rename to fast/stages/2-networking-d-separate-envs/dns-dev.tf
diff --git a/fast/stages/02-networking-separate-envs/dns-prod.tf b/fast/stages/2-networking-d-separate-envs/dns-prod.tf
similarity index 100%
rename from fast/stages/02-networking-separate-envs/dns-prod.tf
rename to fast/stages/2-networking-d-separate-envs/dns-prod.tf
diff --git a/fast/stages/02-networking-separate-envs/main.tf b/fast/stages/2-networking-d-separate-envs/main.tf
similarity index 100%
rename from fast/stages/02-networking-separate-envs/main.tf
rename to fast/stages/2-networking-d-separate-envs/main.tf
diff --git a/fast/stages/02-networking-separate-envs/monitoring.tf b/fast/stages/2-networking-d-separate-envs/monitoring.tf
similarity index 100%
rename from fast/stages/02-networking-separate-envs/monitoring.tf
rename to fast/stages/2-networking-d-separate-envs/monitoring.tf
diff --git a/fast/stages/02-networking-separate-envs/outputs.tf b/fast/stages/2-networking-d-separate-envs/outputs.tf
similarity index 97%
rename from fast/stages/02-networking-separate-envs/outputs.tf
rename to fast/stages/2-networking-d-separate-envs/outputs.tf
index d06d499d63..97b0dc059e 100644
--- a/fast/stages/02-networking-separate-envs/outputs.tf
+++ b/fast/stages/2-networking-d-separate-envs/outputs.tf
@@ -44,13 +44,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/02-networking.auto.tfvars.json"
+ filename = "${try(pathexpand(var.outputs_location), "")}/tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/02-networking.auto.tfvars.json"
+ name = "tfvars/2-networking.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/02-networking-separate-envs/spoke-dev.tf b/fast/stages/2-networking-d-separate-envs/spoke-dev.tf
similarity index 100%
rename from fast/stages/02-networking-separate-envs/spoke-dev.tf
rename to fast/stages/2-networking-d-separate-envs/spoke-dev.tf
diff --git a/fast/stages/02-networking-separate-envs/spoke-prod.tf b/fast/stages/2-networking-d-separate-envs/spoke-prod.tf
similarity index 100%
rename from fast/stages/02-networking-separate-envs/spoke-prod.tf
rename to fast/stages/2-networking-d-separate-envs/spoke-prod.tf
diff --git a/fast/stages/02-networking-separate-envs/test-resources.tf b/fast/stages/2-networking-d-separate-envs/test-resources.tf
similarity index 100%
rename from fast/stages/02-networking-separate-envs/test-resources.tf
rename to fast/stages/2-networking-d-separate-envs/test-resources.tf
diff --git a/fast/stages/02-networking-separate-envs/variables.tf b/fast/stages/2-networking-d-separate-envs/variables.tf
similarity index 92%
rename from fast/stages/02-networking-separate-envs/variables.tf
rename to fast/stages/2-networking-d-separate-envs/variables.tf
index 019d0b2ed4..61262a4215 100644
--- a/fast/stages/02-networking-separate-envs/variables.tf
+++ b/fast/stages/2-networking-d-separate-envs/variables.tf
@@ -15,7 +15,7 @@
*/
variable "automation" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Automation resources created by the bootstrap stage."
type = object({
outputs_bucket = string
@@ -23,12 +23,16 @@ variable "automation" {
}
variable "billing_account" {
- # tfdoc:variable:source 00-bootstrap
- description = "Billing account id and organization id ('nnnnnnnn' or null)."
+ # tfdoc:variable:source 0-bootstrap
+ description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false."
type = object({
- id = string
- organization_id = number
+ id = string
+ is_org_level = optional(bool, true)
})
+ validation {
+ condition = var.billing_account.is_org_level != null
+ error_message = "Invalid `null` value for `billing_account.is_org_level`."
+ }
}
variable "custom_adv" {
@@ -48,7 +52,7 @@ variable "custom_adv" {
}
variable "custom_roles" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Custom roles defined at the org level, in key => id format."
type = object({
service_project_network_admin = string
@@ -72,7 +76,7 @@ variable "dns" {
}
variable "folder_ids" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created."
type = object({
networking = string
@@ -100,7 +104,7 @@ variable "l7ilb_subnets" {
}
variable "organization" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Organization details."
type = object({
domain = string
@@ -116,7 +120,7 @@ variable "outputs_location" {
}
variable "prefix" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Prefix used for resources that need unique names. Use 9 characters or less."
type = string
@@ -187,7 +191,7 @@ variable "router_onprem_configs" {
}
variable "service_accounts" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Automation service accounts in name => email format."
type = object({
data-platform-dev = string
diff --git a/fast/stages/02-networking-separate-envs/vpn-onprem-dev.tf b/fast/stages/2-networking-d-separate-envs/vpn-onprem-dev.tf
similarity index 100%
rename from fast/stages/02-networking-separate-envs/vpn-onprem-dev.tf
rename to fast/stages/2-networking-d-separate-envs/vpn-onprem-dev.tf
diff --git a/fast/stages/02-networking-separate-envs/vpn-onprem-prod.tf b/fast/stages/2-networking-d-separate-envs/vpn-onprem-prod.tf
similarity index 100%
rename from fast/stages/02-networking-separate-envs/vpn-onprem-prod.tf
rename to fast/stages/2-networking-d-separate-envs/vpn-onprem-prod.tf
diff --git a/fast/stages/02-security/IAM.md b/fast/stages/2-security/IAM.md
similarity index 100%
rename from fast/stages/02-security/IAM.md
rename to fast/stages/2-security/IAM.md
diff --git a/fast/stages/02-security/README.md b/fast/stages/2-security/README.md
similarity index 92%
rename from fast/stages/02-security/README.md
rename to fast/stages/2-security/README.md
index 024ababbfe..a609cd8144 100644
--- a/fast/stages/02-security/README.md
+++ b/fast/stages/2-security/README.md
@@ -42,7 +42,7 @@ Some care needs to be taken with project membership in perimeters, which can onl
## How to run this stage
-This stage is meant to be executed after the [resource management](../01-resman) stage has run, as it leverages the folder and automation resources created there. The relevant user groups must also exist, but that's one of the requirements for the previous stages too, so if you ran those successfully, you're good to go.
+This stage is meant to be executed after the [resource management](../1-resman) stage has run, as it leverages the folder and automation resources created there. The relevant user groups must also exist, but that's one of the requirements for the previous stages too, so if you ran those successfully, you're good to go.
It's possible to run this stage in isolation, but that's outside the scope of this document, and you would need to refer to the code for the bootstrap stage for the required roles.
@@ -64,7 +64,7 @@ ln -s ~/fast-config/providers/02-security-providers.tf .
If you have not configured `outputs_location` in resource management, you can derive the providers file from that stage's outputs:
```bash
-cd ../01-resman
+cd ../1-resman
terraform output -json providers | jq -r '.["02-security"]' \
> ../02-security/providers.tf
```
@@ -85,7 +85,7 @@ If you configured a valid path for `outputs_location` in the previous stages, si
ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json .
ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json .
# also copy the tfvars file used for the bootstrap stage
-cp ../00-bootstrap/terraform.tfvars .
+cp ../0-bootstrap/terraform.tfvars .
```
A second set of optional variables is specific to this stage. If you need to customize them add them to the file copied from bootstrap.
@@ -262,20 +262,20 @@ Some references that might be useful in setting up this stage:
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 00-bootstrap
|
-| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…})
| ✓ | | 00-bootstrap
|
-| [folder_ids](variables.tf#L34) | Folder name => id mappings, the 'security' folder name must exist. | object({…})
| ✓ | | 01-resman
|
-| [organization](variables.tf#L80) | Organization details. | object({…})
| ✓ | | 00-bootstrap
|
-| [prefix](variables.tf#L96) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 00-bootstrap
|
-| [service_accounts](variables.tf#L107) | Automation service accounts that can assign the encrypt/decrypt roles on keys. | object({…})
| ✓ | | 01-resman
|
-| [groups](variables.tf#L42) | Group names to grant organization-level permissions. | map(string)
| | {…}
| 00-bootstrap
|
-| [kms_defaults](variables.tf#L57) | Defaults used for KMS keys. | object({…})
| | {…}
| |
-| [kms_keys](variables.tf#L69) | KMS keys to create, keyed by name. Null attributes will be interpolated with defaults. | map(object({…}))
| | {}
| |
-| [outputs_location](variables.tf#L90) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string
| | null
| |
-| [vpc_sc_access_levels](variables.tf#L118) | VPC SC access level definitions. | map(object({…}))
| | {}
| |
-| [vpc_sc_egress_policies](variables.tf#L147) | VPC SC egress policy defnitions. | map(object({…}))
| | {}
| |
-| [vpc_sc_ingress_policies](variables.tf#L167) | VPC SC ingress policy defnitions. | map(object({…}))
| | {}
| |
-| [vpc_sc_perimeters](variables.tf#L188) | VPC SC regular perimeter definitions. | object({…})
| | {}
| |
+| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 0-bootstrap
|
+| [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…})
| ✓ | | 0-bootstrap
|
+| [folder_ids](variables.tf#L38) | Folder name => id mappings, the 'security' folder name must exist. | object({…})
| ✓ | | 1-resman
|
+| [organization](variables.tf#L84) | Organization details. | object({…})
| ✓ | | 0-bootstrap
|
+| [prefix](variables.tf#L100) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 0-bootstrap
|
+| [service_accounts](variables.tf#L111) | Automation service accounts that can assign the encrypt/decrypt roles on keys. | object({…})
| ✓ | | 1-resman
|
+| [groups](variables.tf#L46) | Group names to grant organization-level permissions. | map(string)
| | {…}
| 0-bootstrap
|
+| [kms_defaults](variables.tf#L61) | Defaults used for KMS keys. | object({…})
| | {…}
| |
+| [kms_keys](variables.tf#L73) | KMS keys to create, keyed by name. Null attributes will be interpolated with defaults. | map(object({…}))
| | {}
| |
+| [outputs_location](variables.tf#L94) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string
| | null
| |
+| [vpc_sc_access_levels](variables.tf#L122) | VPC SC access level definitions. | map(object({…}))
| | {}
| |
+| [vpc_sc_egress_policies](variables.tf#L151) | VPC SC egress policy defnitions. | map(object({…}))
| | {}
| |
+| [vpc_sc_ingress_policies](variables.tf#L171) | VPC SC ingress policy defnitions. | map(object({…}))
| | {}
| |
+| [vpc_sc_perimeters](variables.tf#L192) | VPC SC regular perimeter definitions. | object({…})
| | {}
| |
## Outputs
diff --git a/fast/stages/02-security/core-dev.tf b/fast/stages/2-security/core-dev.tf
similarity index 100%
rename from fast/stages/02-security/core-dev.tf
rename to fast/stages/2-security/core-dev.tf
diff --git a/fast/stages/02-security/core-prod.tf b/fast/stages/2-security/core-prod.tf
similarity index 100%
rename from fast/stages/02-security/core-prod.tf
rename to fast/stages/2-security/core-prod.tf
diff --git a/fast/stages/02-security/diagram.png b/fast/stages/2-security/diagram.png
similarity index 100%
rename from fast/stages/02-security/diagram.png
rename to fast/stages/2-security/diagram.png
diff --git a/fast/stages/02-security/diagram.svg b/fast/stages/2-security/diagram.svg
similarity index 100%
rename from fast/stages/02-security/diagram.svg
rename to fast/stages/2-security/diagram.svg
diff --git a/fast/stages/02-security/main.tf b/fast/stages/2-security/main.tf
similarity index 100%
rename from fast/stages/02-security/main.tf
rename to fast/stages/2-security/main.tf
diff --git a/fast/stages/02-security/outputs.tf b/fast/stages/2-security/outputs.tf
similarity index 96%
rename from fast/stages/02-security/outputs.tf
rename to fast/stages/2-security/outputs.tf
index b7e42e4923..ff0c13eda8 100644
--- a/fast/stages/02-security/outputs.tf
+++ b/fast/stages/2-security/outputs.tf
@@ -44,13 +44,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${pathexpand(var.outputs_location)}/tfvars/02-security.auto.tfvars.json"
+ filename = "${pathexpand(var.outputs_location)}/tfvars/2-security.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/02-security.auto.tfvars.json"
+ name = "tfvars/2-security.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/02-security/variables.tf b/fast/stages/2-security/variables.tf
similarity index 91%
rename from fast/stages/02-security/variables.tf
rename to fast/stages/2-security/variables.tf
index 349589c964..e14d637635 100644
--- a/fast/stages/02-security/variables.tf
+++ b/fast/stages/2-security/variables.tf
@@ -15,7 +15,7 @@
*/
variable "automation" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Automation resources created by the bootstrap stage."
type = object({
outputs_bucket = string
@@ -23,16 +23,20 @@ variable "automation" {
}
variable "billing_account" {
- # tfdoc:variable:source 00-bootstrap
- description = "Billing account id and organization id ('nnnnnnnn' or null)."
+ # tfdoc:variable:source 0-bootstrap
+ description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false."
type = object({
- id = string
- organization_id = number
+ id = string
+ is_org_level = optional(bool, true)
})
+ validation {
+ condition = var.billing_account.is_org_level != null
+ error_message = "Invalid `null` value for `billing_account.is_org_level`."
+ }
}
variable "folder_ids" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Folder name => id mappings, the 'security' folder name must exist."
type = object({
security = string
@@ -40,7 +44,7 @@ variable "folder_ids" {
}
variable "groups" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Group names to grant organization-level permissions."
type = map(string)
# https://cloud.google.com/docs/enterprise/setup-checklist
@@ -78,7 +82,7 @@ variable "kms_keys" {
}
variable "organization" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Organization details."
type = object({
domain = string
@@ -94,7 +98,7 @@ variable "outputs_location" {
}
variable "prefix" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Prefix used for resources that need unique names. Use 9 characters or less."
type = string
@@ -105,7 +109,7 @@ variable "prefix" {
}
variable "service_accounts" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Automation service accounts that can assign the encrypt/decrypt roles on keys."
type = object({
data-platform-dev = string
diff --git a/fast/stages/02-security/vpc-sc-restricted-services.yaml b/fast/stages/2-security/vpc-sc-restricted-services.yaml
similarity index 100%
rename from fast/stages/02-security/vpc-sc-restricted-services.yaml
rename to fast/stages/2-security/vpc-sc-restricted-services.yaml
diff --git a/fast/stages/02-security/vpc-sc.tf b/fast/stages/2-security/vpc-sc.tf
similarity index 100%
rename from fast/stages/02-security/vpc-sc.tf
rename to fast/stages/2-security/vpc-sc.tf
diff --git a/fast/stages/03-data-platform/README.md b/fast/stages/3-data-platform/README.md
similarity index 100%
rename from fast/stages/03-data-platform/README.md
rename to fast/stages/3-data-platform/README.md
diff --git a/fast/stages/03-data-platform/dev/IAM.md b/fast/stages/3-data-platform/dev/IAM.md
similarity index 100%
rename from fast/stages/03-data-platform/dev/IAM.md
rename to fast/stages/3-data-platform/dev/IAM.md
diff --git a/fast/stages/03-data-platform/dev/README.md b/fast/stages/3-data-platform/dev/README.md
similarity index 82%
rename from fast/stages/03-data-platform/dev/README.md
rename to fast/stages/3-data-platform/dev/README.md
index 35f9bf1c51..615dbde8b0 100644
--- a/fast/stages/03-data-platform/dev/README.md
+++ b/fast/stages/3-data-platform/dev/README.md
@@ -42,11 +42,11 @@ As per our GCP best practices the Data Platform relies on user groups to assign
### Network
-A Shared VPC is used here, either from one of the FAST networking stages (e.g. [hub and spoke via VPN](../../02-networking-vpn)) or from an external source.
+A Shared VPC is used here, either from one of the FAST networking stages (e.g. [hub and spoke via VPN](../../2-networking-b-vpn)) or from an external source.
### Encryption
-Cloud KMS crypto keys can be configured wither from the [FAST security stage](../../02-security) or from an external source. This step is optional and depends on customer policies and security best practices.
+Cloud KMS crypto keys can be configured wither from the [FAST security stage](../../2-security) or from an external source. This step is optional and depends on customer policies and security best practices.
To configure the use of Cloud KMS on resources, you have to specify the key id on the `service_encryption_keys` variable. Key locations should match resource locations.
@@ -55,19 +55,20 @@ To configure the use of Cloud KMS on resources, you have to specify the key id o
[Data Catalog](https://cloud.google.com/data-catalog) helps you to document your data entry at scale. Data Catalog relies on [tags](https://cloud.google.com/data-catalog/docs/tags-and-tag-templates#tags) and [tag template](https://cloud.google.com/data-catalog/docs/tags-and-tag-templates#tag-templates) to manage metadata for all data entries in a unified and centralized service. To implement [column-level security](https://cloud.google.com/bigquery/docs/column-level-security-intro) on BigQuery, we suggest to use `Tags` and `Tag templates`.
The default configuration will implement 3 tags:
- - `3_Confidential`: policy tag for columns that include very sensitive information, such as credit card numbers.
- - `2_Private`: policy tag for columns that include sensitive personal identifiable information (PII) information, such as a person's first name.
- - `1_Sensitive`: policy tag for columns that include data that cannot be made public, such as the credit limit.
+
+- `3_Confidential`: policy tag for columns that include very sensitive information, such as credit card numbers.
+- `2_Private`: policy tag for columns that include sensitive personal identifiable information (PII) information, such as a person's first name.
+- `1_Sensitive`: policy tag for columns that include data that cannot be made public, such as the credit limit.
Anything that is not tagged is available to all users who have access to the data warehouse.
-You can configure your tags and roles associated by configuring the `data_catalog_tags` variable. We suggest useing the "[Best practices for using policy tags in BigQuery](https://cloud.google.com/bigquery/docs/best-practices-policy-tags)" article as a guide to designing your tags structure and access pattern. By default, no groups has access to tagged data.
+You can configure your tags and roles associated by configuring the `data_catalog_tags` variable. We suggest useing the "[Best practices for using policy tags in BigQuery](https://cloud.google.com/bigquery/docs/best-practices-policy-tags)" article as a guide to designing your tags structure and access pattern. By default, no groups has access to tagged data.
### VPC-SC
-As is often the case in real-world configurations, [VPC-SC](https://cloud.google.com/vpc-service-controls) is needed to mitigate data exfiltration. VPC-SC can be configured from the [FAST security stage](../../02-security). This step is optional, but highly recomended, and depends on customer policies and security best practices.
+As is often the case in real-world configurations, [VPC-SC](https://cloud.google.com/vpc-service-controls) is needed to mitigate data exfiltration. VPC-SC can be configured from the [FAST security stage](../../2-security). This step is optional, but highly recomended, and depends on customer policies and security best practices.
-To configure the use of VPC-SC on the data platform, you have to specify the data platform project numbers on the `vpc_sc_perimeter_projects.dev` variable on [FAST security stage](../../02-security#perimeter-resources).
+To configure the use of VPC-SC on the data platform, you have to specify the data platform project numbers on the `vpc_sc_perimeter_projects.dev` variable on [FAST security stage](../../2-security#perimeter-resources).
In the case your Data Warehouse need to handle confidential data and you have the requirement to separate them deeply from other data and IAM is not enough, the suggested configuration is to keep the confidential project in a separate VPC-SC perimeter with the adequate ingress/egress rules needed for the load and tranformation service account. Below you can find an high level diagram describing the configuration.
@@ -77,7 +78,7 @@ In the case your Data Warehouse need to handle confidential data and you have th
## How to run this stage
-This stage can be run in isolation by prviding the necessary variables, but it's really meant to be used as part of the FAST flow after the "foundational stages" ([`00-bootstrap`](../../00-bootstrap), [`01-resman`](../../01-resman), [`02-networking`](../../02-networking-vpn) and [`02-security`](../../02-security)).
+This stage can be run in isolation by prviding the necessary variables, but it's really meant to be used as part of the FAST flow after the "foundational stages" ([`00-bootstrap`](../../0-bootstrap), [`01-resman`](../../1-resman), [`02-networking`](../../2-networking-b-vpn) and [`02-security`](../../2-security)).
When running in isolation, the following roles are needed on the principal used to apply Terraform:
@@ -111,9 +112,9 @@ ln -s ~/fast-config/providers/03-data-platform-dev-providers.tf .
If you have not configured `outputs_location` in bootstrap, you can derive the providers file from that stage's outputs:
```bash
-cd ../../01-resman
+cd ../../1-resman
terraform output -json providers | jq -r '.["03-data-platform-dev"]' \
- > ../03-data-platform/dev/providers.tf
+ > ../3-data-platform/dev/providers.tf
```
### Variable configuration
@@ -133,7 +134,7 @@ ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json .
ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json .
ln -s ~/fast-config/tfvars/02-networking.auto.tfvars.json .
# also copy the tfvars file used for the bootstrap stage
-cp ../../00-bootstrap/terraform.tfvars .
+cp ../../0-bootstrap/terraform.tfvars .
```
If you're not using FAST or its output files, refer to the [Variables](#variables) table at the bottom of this document for a full list of variables, their origin (e.g., a stage or specific to this one), and descriptions explaining their meaning.
@@ -147,7 +148,7 @@ terraform apply
## Demo pipeline
-The application layer is out of scope of this script. As a demo purpuse only, several Cloud Composer DAGs are provided. Demos will import data from the `landing` area to the `DataWarehouse Confidential` dataset suing different features.
+The application layer is out of scope of this script. As a demo purpuse only, several Cloud Composer DAGs are provided. Demos will import data from the `landing` area to the `DataWarehouse Confidential` dataset suing different features.
You can find examples in the `[demo](../../../../blueprints/data-solutions/data-platform-foundations/demo)` folder.
@@ -166,24 +167,24 @@ You can find examples in the `[demo](../../../../blueprints/data-solutions/data-
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 00-bootstrap
|
-| [billing_account](variables.tf#L25) | Billing account id and organization id ('nnnnnnnn' or null). | object({…})
| ✓ | | 00-globals
|
-| [folder_ids](variables.tf#L98) | Folder to be used for the networking resources in folders/nnnn format. | object({…})
| ✓ | | 01-resman
|
-| [host_project_ids](variables.tf#L116) | Shared VPC project ids. | object({…})
| ✓ | | 02-networking
|
-| [organization](variables.tf#L146) | Organization details. | object({…})
| ✓ | | 00-globals
|
-| [prefix](variables.tf#L162) | Unique prefix used for resource names. Not used for projects if 'project_create' is null. | string
| ✓ | | 00-globals
|
-| [composer_config](variables.tf#L34) | Cloud Composer configuration options. | object({…})
| | {…}
| |
-| [data_catalog_tags](variables.tf#L81) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(map(list(string)))
| | {…}
| |
-| [data_force_destroy](variables.tf#L92) | Flag to set 'force_destroy' on data services like BigQery or Cloud Storage. | bool
| | false
| |
-| [groups](variables.tf#L106) | Groups. | map(string)
| | {…}
| |
-| [location](variables.tf#L124) | Location used for multi-regional resources. | string
| | "eu"
| |
-| [network_config_composer](variables.tf#L130) | Network configurations to use for Composer. | object({…})
| | {…}
| |
-| [outputs_location](variables.tf#L156) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string
| | null
| |
-| [project_services](variables.tf#L168) | List of core services enabled on all projects. | list(string)
| | […]
| |
-| [region](variables.tf#L179) | Region used for regional resources. | string
| | "europe-west1"
| |
-| [service_encryption_keys](variables.tf#L185) | Cloud KMS to use to encrypt different services. Key location should match service region. | object({…})
| | null
| |
-| [subnet_self_links](variables.tf#L197) | Shared VPC subnet self links. | object({…})
| | null
| 02-networking
|
-| [vpc_self_links](variables.tf#L206) | Shared VPC self links. | object({…})
| | null
| 02-networking
|
+| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 0-bootstrap
|
+| [billing_account](variables.tf#L25) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…})
| ✓ | | 0-bootstrap
|
+| [folder_ids](variables.tf#L102) | Folder to be used for the networking resources in folders/nnnn format. | object({…})
| ✓ | | 1-resman
|
+| [host_project_ids](variables.tf#L120) | Shared VPC project ids. | object({…})
| ✓ | | 2-networking
|
+| [organization](variables.tf#L150) | Organization details. | object({…})
| ✓ | | 00-globals
|
+| [prefix](variables.tf#L166) | Unique prefix used for resource names. Not used for projects if 'project_create' is null. | string
| ✓ | | 00-globals
|
+| [composer_config](variables.tf#L38) | Cloud Composer configuration options. | object({…})
| | {…}
| |
+| [data_catalog_tags](variables.tf#L85) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(map(list(string)))
| | {…}
| |
+| [data_force_destroy](variables.tf#L96) | Flag to set 'force_destroy' on data services like BigQery or Cloud Storage. | bool
| | false
| |
+| [groups](variables.tf#L110) | Groups. | map(string)
| | {…}
| |
+| [location](variables.tf#L128) | Location used for multi-regional resources. | string
| | "eu"
| |
+| [network_config_composer](variables.tf#L134) | Network configurations to use for Composer. | object({…})
| | {…}
| |
+| [outputs_location](variables.tf#L160) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string
| | null
| |
+| [project_services](variables.tf#L172) | List of core services enabled on all projects. | list(string)
| | […]
| |
+| [region](variables.tf#L183) | Region used for regional resources. | string
| | "europe-west1"
| |
+| [service_encryption_keys](variables.tf#L189) | Cloud KMS to use to encrypt different services. Key location should match service region. | object({…})
| | null
| |
+| [subnet_self_links](variables.tf#L201) | Shared VPC subnet self links. | object({…})
| | null
| 2-networking
|
+| [vpc_self_links](variables.tf#L210) | Shared VPC self links. | object({…})
| | null
| 2-networking
|
## Outputs
diff --git a/fast/stages/03-data-platform/dev/demo b/fast/stages/3-data-platform/dev/demo
similarity index 100%
rename from fast/stages/03-data-platform/dev/demo
rename to fast/stages/3-data-platform/dev/demo
diff --git a/fast/stages/03-data-platform/dev/diagram.png b/fast/stages/3-data-platform/dev/diagram.png
similarity index 100%
rename from fast/stages/03-data-platform/dev/diagram.png
rename to fast/stages/3-data-platform/dev/diagram.png
diff --git a/fast/stages/03-data-platform/dev/diagram_vpcsc.png b/fast/stages/3-data-platform/dev/diagram_vpcsc.png
similarity index 100%
rename from fast/stages/03-data-platform/dev/diagram_vpcsc.png
rename to fast/stages/3-data-platform/dev/diagram_vpcsc.png
diff --git a/fast/stages/03-data-platform/dev/main.tf b/fast/stages/3-data-platform/dev/main.tf
similarity index 100%
rename from fast/stages/03-data-platform/dev/main.tf
rename to fast/stages/3-data-platform/dev/main.tf
diff --git a/fast/stages/03-data-platform/dev/outputs.tf b/fast/stages/3-data-platform/dev/outputs.tf
similarity index 95%
rename from fast/stages/03-data-platform/dev/outputs.tf
rename to fast/stages/3-data-platform/dev/outputs.tf
index d0f79358cb..2eb813b4d1 100644
--- a/fast/stages/03-data-platform/dev/outputs.tf
+++ b/fast/stages/3-data-platform/dev/outputs.tf
@@ -27,13 +27,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${pathexpand(var.outputs_location)}/tfvars/03-data-platform-dev.auto.tfvars.json"
+ filename = "${pathexpand(var.outputs_location)}/tfvars/3-data-platform-dev.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/03-data-platform-dev.auto.tfvars.json"
+ name = "tfvars/3-data-platform-dev.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/03-data-platform/dev/variables.tf b/fast/stages/3-data-platform/dev/variables.tf
similarity index 90%
rename from fast/stages/03-data-platform/dev/variables.tf
rename to fast/stages/3-data-platform/dev/variables.tf
index 29dd1e45a4..74a5dbe11d 100644
--- a/fast/stages/03-data-platform/dev/variables.tf
+++ b/fast/stages/3-data-platform/dev/variables.tf
@@ -15,7 +15,7 @@
# tfdoc:file:description Terraform Variables.
variable "automation" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Automation resources created by the bootstrap stage."
type = object({
outputs_bucket = string
@@ -23,12 +23,16 @@ variable "automation" {
}
variable "billing_account" {
- # tfdoc:variable:source 00-globals
- description = "Billing account id and organization id ('nnnnnnnn' or null)."
+ # tfdoc:variable:source 0-bootstrap
+ description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false."
type = object({
- id = string
- organization_id = number
+ id = string
+ is_org_level = optional(bool, true)
})
+ validation {
+ condition = var.billing_account.is_org_level != null
+ error_message = "Invalid `null` value for `billing_account.is_org_level`."
+ }
}
variable "composer_config" {
@@ -96,7 +100,7 @@ variable "data_force_destroy" {
}
variable "folder_ids" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Folder to be used for the networking resources in folders/nnnn format."
type = object({
data-platform-dev = string
@@ -114,7 +118,7 @@ variable "groups" {
}
variable "host_project_ids" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Shared VPC project ids."
type = object({
dev-spoke-0 = string
@@ -195,7 +199,7 @@ variable "service_encryption_keys" {
}
variable "subnet_self_links" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Shared VPC subnet self links."
type = object({
dev-spoke-0 = map(string)
@@ -204,7 +208,7 @@ variable "subnet_self_links" {
}
variable "vpc_self_links" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Shared VPC self links."
type = object({
dev-spoke-0 = string
diff --git a/fast/stages/03-gke-multitenant/README.md b/fast/stages/3-gke-multitenant/README.md
similarity index 71%
rename from fast/stages/03-gke-multitenant/README.md
rename to fast/stages/3-gke-multitenant/README.md
index f08910c834..9f9d9498e8 100644
--- a/fast/stages/03-gke-multitenant/README.md
+++ b/fast/stages/3-gke-multitenant/README.md
@@ -2,7 +2,7 @@
This directory contains a stage that can be used to centralize management of GKE multinenant clusters.
-The Terraform code follows the same general approach used for the [project factory](../03-project-factory/) and [data platform](../03-data-platform/) stages, where a "fat module" contains the stage code and is used by thin code wrappers that localize it for each environment or specialized configuration:
+The Terraform code follows the same general approach used for the [project factory](../3-project-factory/) and [data platform](../3-data-platform/) stages, where a "fat module" contains the stage code and is used by thin code wrappers that localize it for each environment or specialized configuration:
The [`dev` folder](./dev/) contains an example setup for a generic development environment, and can be used as-is or cloned to implement other environments, or more specialized setups
diff --git a/fast/stages/03-gke-multitenant/dev/README.md b/fast/stages/3-gke-multitenant/dev/README.md
similarity index 90%
rename from fast/stages/03-gke-multitenant/dev/README.md
rename to fast/stages/3-gke-multitenant/dev/README.md
index c446fbcb4a..4accf8e1aa 100644
--- a/fast/stages/03-gke-multitenant/dev/README.md
+++ b/fast/stages/3-gke-multitenant/dev/README.md
@@ -39,7 +39,7 @@ This stage creates a project containing and as many clusters and node pools as r
## How to run this stage
-This stage is meant to be executed after "foundational stages" (i.e., stages [`00-bootstrap`](../../00-bootstrap), [`01-resman`](../../01-resman), 02-networking (either [VPN](../../02-networking-vpn) or [NVA](../../02-networking-nva)) and [`02-security`](../../02-security)) have been run.
+This stage is meant to be executed after "foundational stages" (i.e., stages [`00-bootstrap`](../../0-bootstrap), [`01-resman`](../../1-resman), 02-networking (either [VPN](../../2-networking-b-vpn) or [NVA](../../2-networking-c-nva)) and [`02-security`](../../2-security)) have been run.
It's of course possible to run this stage in isolation, by making sure the architectural prerequisites are satisfied (e.g., networking), and that the Service Account running the stage is granted the roles/permissions below:
@@ -140,23 +140,23 @@ terraform apply
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [automation](variables.tf#L21) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 00-bootstrap
|
-| [billing_account](variables.tf#L29) | Billing account id and organization id ('nnnnnnnn' or null). | object({…})
| ✓ | | 00-bootstrap
|
-| [folder_ids](variables.tf#L149) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…})
| ✓ | | 01-resman
|
-| [host_project_ids](variables.tf#L164) | Host project for the shared VPC. | object({…})
| ✓ | | 02-networking
|
-| [prefix](variables.tf#L213) | Prefix used for resources that need unique names. | string
| ✓ | | |
-| [vpc_self_links](variables.tf#L225) | Self link for the shared VPC. | object({…})
| ✓ | | 02-networking
|
-| [clusters](variables.tf#L38) | Clusters configuration. Refer to the gke-cluster module for type details. | map(object({…}))
| | {}
| |
-| [fleet_configmanagement_clusters](variables.tf#L86) | Config management features enabled on specific sets of member clusters, in config name => [cluster name] format. | map(list(string))
| | {}
| |
-| [fleet_configmanagement_templates](variables.tf#L94) | Sets of config management configurations that can be applied to member clusters, in config name => {options} format. | map(object({…}))
| | {}
| |
-| [fleet_features](variables.tf#L129) | Enable and configue fleet features. Set to null to disable GKE Hub if fleet workload identity is not used. | object({…})
| | null
| |
-| [fleet_workload_identity](variables.tf#L142) | Use Fleet Workload Identity for clusters. Enables GKE Hub if set to true. | bool
| | false
| |
-| [group_iam](variables.tf#L157) | Project-level authoritative IAM bindings for groups in {GROUP_EMAIL => [ROLES]} format. Use group emails as keys, list of roles as values. | map(list(string))
| | {}
| |
-| [iam](variables.tf#L172) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
| |
-| [labels](variables.tf#L179) | Project-level labels. | map(string)
| | {}
| |
-| [nodepools](variables.tf#L185) | Nodepools configuration. Refer to the gke-nodepool module for type details. | map(map(object({…})))
| | {}
| |
-| [outputs_location](variables.tf#L207) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string
| | null
| |
-| [project_services](variables.tf#L218) | Additional project services to enable. | list(string)
| | []
| |
+| [automation](variables.tf#L21) | Automation resources created by the bootstrap stage. | object({…})
| ✓ | | 0-bootstrap
|
+| [billing_account](variables.tf#L29) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…})
| ✓ | | 0-bootstrap
|
+| [folder_ids](variables.tf#L153) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…})
| ✓ | | 1-resman
|
+| [host_project_ids](variables.tf#L168) | Host project for the shared VPC. | object({…})
| ✓ | | 2-networking
|
+| [prefix](variables.tf#L217) | Prefix used for resources that need unique names. | string
| ✓ | | |
+| [vpc_self_links](variables.tf#L229) | Self link for the shared VPC. | object({…})
| ✓ | | 2-networking
|
+| [clusters](variables.tf#L42) | Clusters configuration. Refer to the gke-cluster module for type details. | map(object({…}))
| | {}
| |
+| [fleet_configmanagement_clusters](variables.tf#L90) | Config management features enabled on specific sets of member clusters, in config name => [cluster name] format. | map(list(string))
| | {}
| |
+| [fleet_configmanagement_templates](variables.tf#L98) | Sets of config management configurations that can be applied to member clusters, in config name => {options} format. | map(object({…}))
| | {}
| |
+| [fleet_features](variables.tf#L133) | Enable and configue fleet features. Set to null to disable GKE Hub if fleet workload identity is not used. | object({…})
| | null
| |
+| [fleet_workload_identity](variables.tf#L146) | Use Fleet Workload Identity for clusters. Enables GKE Hub if set to true. | bool
| | false
| |
+| [group_iam](variables.tf#L161) | Project-level authoritative IAM bindings for groups in {GROUP_EMAIL => [ROLES]} format. Use group emails as keys, list of roles as values. | map(list(string))
| | {}
| |
+| [iam](variables.tf#L176) | Project-level authoritative IAM bindings for users and service accounts in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
| |
+| [labels](variables.tf#L183) | Project-level labels. | map(string)
| | {}
| |
+| [nodepools](variables.tf#L189) | Nodepools configuration. Refer to the gke-nodepool module for type details. | map(map(object({…})))
| | {}
| |
+| [outputs_location](variables.tf#L211) | Path where providers, tfvars files, and lists for the following stages are written. Leave empty to disable. | string
| | null
| |
+| [project_services](variables.tf#L222) | Additional project services to enable. | list(string)
| | []
| |
## Outputs
diff --git a/fast/stages/03-gke-multitenant/dev/diagram.png b/fast/stages/3-gke-multitenant/dev/diagram.png
similarity index 100%
rename from fast/stages/03-gke-multitenant/dev/diagram.png
rename to fast/stages/3-gke-multitenant/dev/diagram.png
diff --git a/fast/stages/03-gke-multitenant/dev/main.tf b/fast/stages/3-gke-multitenant/dev/main.tf
similarity index 100%
rename from fast/stages/03-gke-multitenant/dev/main.tf
rename to fast/stages/3-gke-multitenant/dev/main.tf
diff --git a/fast/stages/03-gke-multitenant/dev/outputs.tf b/fast/stages/3-gke-multitenant/dev/outputs.tf
similarity index 96%
rename from fast/stages/03-gke-multitenant/dev/outputs.tf
rename to fast/stages/3-gke-multitenant/dev/outputs.tf
index 87b0ca737c..3f231c6820 100644
--- a/fast/stages/03-gke-multitenant/dev/outputs.tf
+++ b/fast/stages/3-gke-multitenant/dev/outputs.tf
@@ -42,13 +42,13 @@ locals {
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
- filename = "${pathexpand(var.outputs_location)}/tfvars/03-gke-dev.auto.tfvars.json"
+ filename = "${pathexpand(var.outputs_location)}/tfvars/3-gke-dev.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
- name = "tfvars/03-gke-dev.auto.tfvars.json"
+ name = "tfvars/3-gke-dev.auto.tfvars.json"
content = jsonencode(local.tfvars)
}
diff --git a/fast/stages/03-gke-multitenant/dev/variables.tf b/fast/stages/3-gke-multitenant/dev/variables.tf
similarity index 92%
rename from fast/stages/03-gke-multitenant/dev/variables.tf
rename to fast/stages/3-gke-multitenant/dev/variables.tf
index 6be89126a5..2dbf5a6ea5 100644
--- a/fast/stages/03-gke-multitenant/dev/variables.tf
+++ b/fast/stages/3-gke-multitenant/dev/variables.tf
@@ -19,7 +19,7 @@
# cloud dns for gke?
variable "automation" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Automation resources created by the bootstrap stage."
type = object({
outputs_bucket = string
@@ -27,12 +27,16 @@ variable "automation" {
}
variable "billing_account" {
- # tfdoc:variable:source 00-bootstrap
- description = "Billing account id and organization id ('nnnnnnnn' or null)."
+ # tfdoc:variable:source 0-bootstrap
+ description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false."
type = object({
- id = string
- organization_id = number
+ id = string
+ is_org_level = optional(bool, true)
})
+ validation {
+ condition = var.billing_account.is_org_level != null
+ error_message = "Invalid `null` value for `billing_account.is_org_level`."
+ }
}
variable "clusters" {
@@ -147,7 +151,7 @@ variable "fleet_workload_identity" {
}
variable "folder_ids" {
- # tfdoc:variable:source 01-resman
+ # tfdoc:variable:source 1-resman
description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created."
type = object({
gke-dev = string
@@ -162,7 +166,7 @@ variable "group_iam" {
}
variable "host_project_ids" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Host project for the shared VPC."
type = object({
dev-spoke-0 = string
@@ -223,7 +227,7 @@ variable "project_services" {
}
variable "vpc_self_links" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Self link for the shared VPC."
type = object({
dev-spoke-0 = string
diff --git a/fast/stages/03-project-factory/README.md b/fast/stages/3-project-factory/README.md
similarity index 100%
rename from fast/stages/03-project-factory/README.md
rename to fast/stages/3-project-factory/README.md
diff --git a/fast/stages/03-project-factory/dev/README.md b/fast/stages/3-project-factory/dev/README.md
similarity index 85%
rename from fast/stages/03-project-factory/dev/README.md
rename to fast/stages/3-project-factory/dev/README.md
index 8fe213cee9..2d95f918d7 100644
--- a/fast/stages/03-project-factory/dev/README.md
+++ b/fast/stages/3-project-factory/dev/README.md
@@ -28,7 +28,7 @@ The project factory takes care of the following activities:
## How to run this stage
-This stage is meant to be executed after "foundational stages" (i.e., stages [`00-bootstrap`](../../00-bootstrap), [`01-resman`](../../01-resman), 02-networking (either [VPN](../../02-networking-vpn) or [NVA](../../02-networking-nva)) and [`02-security`](../../02-security)) have been run.
+This stage is meant to be executed after "foundational stages" (i.e., stages [`00-bootstrap`](../../0-bootstrap), [`01-resman`](../../1-resman), 02-networking (either [VPN](../../2-networking-b-vpn) or [NVA](../../2-networking-c-nva)) and [`02-security`](../../2-security)) have been run.
It's of course possible to run this stage in isolation, by making sure the architectural prerequisites are satisfied (e.g., networking), and that the Service Account running the stage is granted the roles/permissions below:
@@ -108,13 +108,13 @@ terraform apply
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
-| [billing_account](variables.tf#L19) | Billing account id and organization id ('nnnnnnnn' or null). | object({…})
| ✓ | | 00-bootstrap
|
-| [prefix](variables.tf#L56) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 00-bootstrap
|
-| [data_dir](variables.tf#L28) | Relative path for the folder storing configuration data. | string
| | "data/projects"
| |
-| [defaults_file](variables.tf#L34) | Relative path for the file storing the project factory configuration. | string
| | "data/defaults.yaml"
| |
-| [environment_dns_zone](variables.tf#L40) | DNS zone suffix for environment. | string
| | null
| 02-networking
|
-| [host_project_ids](variables.tf#L47) | Host project for the shared VPC. | object({…})
| | null
| 02-networking
|
-| [vpc_self_links](variables.tf#L67) | Self link for the shared VPC. | object({…})
| | null
| 02-networking
|
+| [billing_account](variables.tf#L19) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…})
| ✓ | | 0-bootstrap
|
+| [prefix](variables.tf#L60) | Prefix used for resources that need unique names. Use 9 characters or less. | string
| ✓ | | 0-bootstrap
|
+| [data_dir](variables.tf#L32) | Relative path for the folder storing configuration data. | string
| | "data/projects"
| |
+| [defaults_file](variables.tf#L38) | Relative path for the file storing the project factory configuration. | string
| | "data/defaults.yaml"
| |
+| [environment_dns_zone](variables.tf#L44) | DNS zone suffix for environment. | string
| | null
| 2-networking
|
+| [host_project_ids](variables.tf#L51) | Host project for the shared VPC. | object({…})
| | null
| 2-networking
|
+| [vpc_self_links](variables.tf#L71) | Self link for the shared VPC. | object({…})
| | null
| 2-networking
|
## Outputs
diff --git a/fast/stages/03-project-factory/dev/data/defaults.yaml b/fast/stages/3-project-factory/dev/data/defaults.yaml
similarity index 100%
rename from fast/stages/03-project-factory/dev/data/defaults.yaml
rename to fast/stages/3-project-factory/dev/data/defaults.yaml
diff --git a/fast/stages/03-project-factory/dev/data/projects/project.yaml.sample b/fast/stages/3-project-factory/dev/data/projects/project.yaml.sample
similarity index 100%
rename from fast/stages/03-project-factory/dev/data/projects/project.yaml.sample
rename to fast/stages/3-project-factory/dev/data/projects/project.yaml.sample
diff --git a/fast/stages/03-project-factory/dev/diagram.png b/fast/stages/3-project-factory/dev/diagram.png
similarity index 100%
rename from fast/stages/03-project-factory/dev/diagram.png
rename to fast/stages/3-project-factory/dev/diagram.png
diff --git a/fast/stages/03-project-factory/dev/diagram.svg b/fast/stages/3-project-factory/dev/diagram.svg
similarity index 100%
rename from fast/stages/03-project-factory/dev/diagram.svg
rename to fast/stages/3-project-factory/dev/diagram.svg
diff --git a/fast/stages/03-project-factory/dev/main.tf b/fast/stages/3-project-factory/dev/main.tf
similarity index 100%
rename from fast/stages/03-project-factory/dev/main.tf
rename to fast/stages/3-project-factory/dev/main.tf
diff --git a/fast/stages/03-project-factory/dev/outputs.tf b/fast/stages/3-project-factory/dev/outputs.tf
similarity index 100%
rename from fast/stages/03-project-factory/dev/outputs.tf
rename to fast/stages/3-project-factory/dev/outputs.tf
diff --git a/fast/stages/03-project-factory/dev/variables.tf b/fast/stages/3-project-factory/dev/variables.tf
similarity index 76%
rename from fast/stages/03-project-factory/dev/variables.tf
rename to fast/stages/3-project-factory/dev/variables.tf
index 2993bfba7b..5ad49f7720 100644
--- a/fast/stages/03-project-factory/dev/variables.tf
+++ b/fast/stages/3-project-factory/dev/variables.tf
@@ -17,12 +17,16 @@
#TODO: tfdoc annotations
variable "billing_account" {
- # tfdoc:variable:source 00-bootstrap
- description = "Billing account id and organization id ('nnnnnnnn' or null)."
+ # tfdoc:variable:source 0-bootstrap
+ description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false."
type = object({
- id = string
- organization_id = number
+ id = string
+ is_org_level = optional(bool, true)
})
+ validation {
+ condition = var.billing_account.is_org_level != null
+ error_message = "Invalid `null` value for `billing_account.is_org_level`."
+ }
}
variable "data_dir" {
@@ -38,14 +42,14 @@ variable "defaults_file" {
}
variable "environment_dns_zone" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "DNS zone suffix for environment."
type = string
default = null
}
variable "host_project_ids" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Host project for the shared VPC."
type = object({
dev-spoke-0 = string
@@ -54,7 +58,7 @@ variable "host_project_ids" {
}
variable "prefix" {
- # tfdoc:variable:source 00-bootstrap
+ # tfdoc:variable:source 0-bootstrap
description = "Prefix used for resources that need unique names. Use 9 characters or less."
type = string
@@ -65,7 +69,7 @@ variable "prefix" {
}
variable "vpc_self_links" {
- # tfdoc:variable:source 02-networking
+ # tfdoc:variable:source 2-networking
description = "Self link for the shared VPC."
type = object({
dev-spoke-0 = string
diff --git a/fast/stages/CLEANUP.md b/fast/stages/CLEANUP.md
index e5f1418f75..3bc581f92a 100644
--- a/fast/stages/CLEANUP.md
+++ b/fast/stages/CLEANUP.md
@@ -1,4 +1,5 @@
# FAST deployment clean up
+
If you want to destroy a previous FAST deployment in your organization, follow these steps.
Destruction must be done in reverse order, from stage 3 to stage 0
@@ -11,6 +12,7 @@ terraform destroy
```
## Stage 3 (GKE)
+
Terraform refuses to delete non-empty GCS buckets and BigQuery datasets, so they need to be removed manually from the state.
```bash
@@ -24,14 +26,15 @@ done
terraform destroy
```
-
## Stage 2 (Security)
+
```bash
cd $FAST_PWD/02-security/
terraform destroy
```
## Stage 2 (Networking)
+
```bash
cd $FAST_PWD/02-networking-XXX/
terraform destroy
@@ -43,7 +46,6 @@ A minor glitch can surface running `terraform destroy`, where the service projec
Stage 1 is a little more complicated because of the GCS buckets containing your terraform statefiles. By default, Terraform refuses to delete non-empty buckets, which is good to protect your terraform state, but it makes destruction a bit harder. Use the commands below to remove the GCS buckets from the state and then execute `terraform destroy`
-
```bash
cd $FAST_PWD/01-resman/
@@ -110,5 +112,6 @@ rm -i terraform.tfstate*
```
In case you want to deploy FAST stages again, the make sure to:
-* Modify the [prefix](00-bootstrap/variables.tf) variable to allow the deployment of resources that need unique names (eg, projects).
-* Modify the [custom_roles](00-bootstrap/variables.tf) variable to allow recently deleted custom roles to be created again.
+
+* Modify the [prefix](0-bootstrap/variables.tf) variable to allow the deployment of resources that need unique names (eg, projects).
+* Modify the [custom_roles](0-bootstrap/variables.tf) variable to allow recently deleted custom roles to be created again.
diff --git a/fast/stages/COMPANION.md b/fast/stages/COMPANION.md
index e0f6ec6215..d5d7752f2e 100644
--- a/fast/stages/COMPANION.md
+++ b/fast/stages/COMPANION.md
@@ -1,32 +1,42 @@
# FAST deployment companion guide
-To deploy a GCP Landing Zone using FAST, your organization needs to meet a few prerequisites before starting. This guide serves as quick guide to prepare your GCP organization and also as cheat sheet with the commands and minimal configuration required to deploy FAST.
+To deploy a GCP Landing Zone using FAST, your organization needs to meet a few prerequisites before starting. This guide serves as quick guide to prepare your GCP organization and also as cheat sheet with the commands and minimal configuration required to deploy FAST.
The detailed explanation of each stage, their configuration, possible modifications and adaptations are included in the README of stage. This document only outlines the minimal configuration to get from an empty organization to a working FAST deployment.
**Warning! Executing FAST sets organization policies and authoritative role bindings in your GCP Organization. We recommend using FAST on a clean organization, or to fork and adapt FAST to support your existing Organization needs.**
## Prerequisites
+
1. FAST uses the recommended groups from the [GCP Enterprise Setup checklist](). Go to [Workspace / Cloud Identity](https://admin.google.com) and ensure all the following groups exist:
- - `gcp-billing-admins@`
- - `gcp-devops@`
- - `gcp-network-admins@`
- - `gcp-organization-admins@`
- - `gcp-security-admins@`
- - `gcp-support@`
+
+- `gcp-billing-admins@`
+- `gcp-devops@`
+- `gcp-network-admins@`
+- `gcp-organization-admins@`
+- `gcp-security-admins@`
+- `gcp-support@`
+
2. If you already executed FAST in your organization, make you [clean it up](CLEANUP.md) before continuing with the rest of this guide.
+
3. Grant your user “Organization Administrator” role in your organization and add it to the `gcp-organization-admins@` group.
+
4. Login with your user using gcloud.
+
```bash
gcloud auth login
gcloud auth application-default login
```
+
5. Clone the Fabric repository.
+
```bash
git clone https://github.com/GoogleCloudPlatform/cloud-foundation-fabric.git
cd cloud-foundation-fabric
```
+
6. Grant required roles to your user.
+
```bash
# set a variable to the fast folder
export FAST_PWD="$(pwd)/fast/stages"
@@ -49,9 +59,11 @@ gcloud organizations add-iam-policy-binding $FAST_ORG_ID \
--member user:$FAST_BU --role $role
done
```
-7. Configure Billing Account permissions.
+
+7. Configure Billing Account permissions.
If you are using a standalone billing account, the user applying this stage for the first time needs to be a Billing Administrator.
+
```bash
# find your billing account id with gcloud beta billing accounts list
# replace with your billing id!
@@ -60,9 +72,11 @@ export FAST_BA_ID=XXXXXX-YYYYYY-ZZZZZZ
gcloud beta billing accounts add-iam-policy-binding $FAST_BA_ID \
--member user:$FAST_BU --role roles/billing.admin
```
-If you are using a billing account in a different organization, please follow [these steps](00-bootstrap#billing-account-in-a-different-organization) instead.
+
+If you are using a billing account in a different organization, please follow [these steps](0-bootstrap#billing-account-in-a-different-organization) instead.
## Stage 0 (Bootstrap)
+
This initial stage will create common projects for IaC, Logging & Billing, and bootstrap IAM policies.
```bash
@@ -73,7 +87,9 @@ cd $FAST_PWD/00-bootstrap
# then edit to match your environment!
edit terraform.tfvars.sample
```
+
Here you have a terraform.tfvars example:
+
```hcl
# fetch the required id by running `gcloud beta billing accounts list`
billing_account={
@@ -99,7 +115,7 @@ terraform init
terraform apply -var bootstrap_user=$FAST_BU
# link the generated provider file
-ln -s ~/fast-config/providers/00-bootstrap* .
+ln -s ~/fast-config/providers/0-0-bootstrap* .
# re-run init and apply to remove user-level IAM
terraform init -migrate-state
@@ -108,22 +124,27 @@ terraform apply
```
## Stage 1 (Resource Management)
+
This stage performs two important tasks:
+
- Create the top-level hierarchy of folders, and the associated resources used later on to automate each part of the hierarchy (eg. Networking).
- Set organization policies on the organization, and any exception required on specific folders.
+
```bash
# move to the 01-resman directory
cd $FAST_PWD/01-resman
# Link providers and variables from previous stages
-ln -s ~/fast-config/providers/01-resman-providers.tf .
-ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json .
+ln -s ~/fast-config/providers/1-0-resman-providers.tf .
+ln -s ~/fast-config/tfvars/0-0-bootstrap.auto.tfvars.json .
ln -s ~/fast-config/tfvars/globals.auto.tfvars.json .
# Edit your terraform.tfvars to append Teams configuration (optional)
edit terraform.tfvars
```
+
In the following terraform.tfvars it is shown an example of configuration for teams provisioning:
+
```hcl
outputs_location = "~/fast-config"
@@ -140,6 +161,7 @@ team_folders = {
}
}
```
+
```bash
# run init and apply
terraform init
@@ -147,28 +169,34 @@ terraform apply
```
## Stage 2 (Networking)
+
In this stage, we will deploy one of the 3 available Hub&Spoke networking topologies:
+
1. VPC Peering
2. HA VPN
3. Multi-NIC appliances (NVA)
+
```bash
# move to the 02-networking-XXX directory (where XXX should be one of vpn|peering|nva)
cd $FAST_PWD/02-networking-XXX
# setup providers and variables from previous stages
-ln -s ~/fast-config/providers/02-networking-providers.tf .
-ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json .
-ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json .
+ln -s ~/fast-config/providers/2-0-networking-providers.tf .
+ln -s ~/fast-config/tfvars/0-0-bootstrap.auto.tfvars.json .
+ln -s ~/fast-config/tfvars/1-0-resman.auto.tfvars.json .
ln -s ~/fast-config/tfvars/globals.auto.tfvars.json .
# Create terraform.tfvars. output_location variable is required to generate networking stage output file
edit terraform.tfvars
```
+
In the following terraform.tfvars we configure output_location variable to generate networking stage output file:
+
```hcl
# path for automatic generation of configs
outputs_location = "~/fast-config"
```
+
```bash
# run init and apply
terraform init
@@ -176,21 +204,25 @@ terraform apply
```
## Stage 2 (Security)
+
This stage sets up security resources (KMS and VPC-SC) and configurations which impact the whole organization, or are shared across the hierarchy to other projects and teams.
+
```bash
# move to the 02-security directory
cd $FAST_PWD/02-security
# link providers and variables from previous stages
-ln -s ~/fast-config/providers/02-security-providers.tf .
-ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json .
-ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json .
+ln -s ~/fast-config/providers/2-0-security-providers.tf .
+ln -s ~/fast-config/tfvars/0-0-bootstrap.auto.tfvars.json .
+ln -s ~/fast-config/tfvars/1-0-resman.auto.tfvars.json .
ln -s ~/fast-config/tfvars/globals.auto.tfvars.json .
# Edit terraform.tfvars to include KMS and/or VPC-SC configuration
edit terraform.tfvars
```
-Some examples of terraform.tfvars configurations for KMS and VPC-SC can be found [here](02-security#customizations)
+
+Some examples of terraform.tfvars configurations for KMS and VPC-SC can be found [here](2-security#customizations)
+
```bash
# run init and apply
terraform init
@@ -198,15 +230,17 @@ terraform apply
```
## Stage 3 (Project Factory)
+
The Project Factory stage builds on top of your foundations to create and set up projects (and related resources) to be used for your workloads. It is organized in folders representing environments (e.g. "dev", "prod"), each implemented by a stand-alone terraform resource factory.
+
```bash
# Variable `outputs_location` is set to `~/fast-config`
-cd $FAST_PWD/03-project-factory/ENVIRONMENT
-ln -s ~/fast-config/providers/03-project-factory-ENVIRONMENT-providers.tf .
+cd $FAST_PWD/3-0-project-factory/ENVIRONMENT
+ln -s ~/fast-config/providers/3-0-project-factory-ENVIRONMENT-providers.tf .
-ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json .
-ln -s ~/fast-config/tfvars/01-resman.auto.tfvars.json .
-ln -s ~/fast-config/tfvars/02-networking.auto.tfvars.json .
+ln -s ~/fast-config/tfvars/0-0-bootstrap.auto.tfvars.json .
+ln -s ~/fast-config/tfvars/1-0-resman.auto.tfvars.json .
+ln -s ~/fast-config/tfvars/2-0-networking.auto.tfvars.json .
ln -s ~/fast-config/tfvars/globals.auto.tfvars.json .
# Define your environment default values (eg for billing alerts and labels)
diff --git a/fast/stages/README.md b/fast/stages/README.md
index 9b41bf1cae..acb14be6c4 100644
--- a/fast/stages/README.md
+++ b/fast/stages/README.md
@@ -1,4 +1,4 @@
-# Fast stages
+# FAST stages
Each of the folders contained here is a separate "stage", or Terraform root module.
@@ -9,7 +9,7 @@ When combined together, each stage is designed to leverage the previous stage's
This has two important consequences
- any stage can be swapped out and replaced by different code as long as it respects the contract by providing a predefined set of outputs and optionally accepting a predefined set of variables
-- data flow between stages can be partially automated (see [stage 00 documentation on output files](./00-bootstrap/README.md#output-files-and-cross-stage-variables)), reducing the effort and pain required to compile variables by hand
+- data flow between stages can be partially automated (see [stage 00 documentation on output files](./0-bootstrap/README.md#output-files-and-cross-stage-variables)), reducing the effort and pain required to compile variables by hand
One important assumption is that the flow of data is always forward looking, so no stage needs to depend on outputs generated further down the chain. This greatly simplifies both the logic and the implementation, and allows stages to be effectively independent.
@@ -19,28 +19,32 @@ Refer to each stage's documentation for a detailed description of its purpose, t
To destroy a previous FAST deployment follow the instructions detailed in [cleanup](CLEANUP.md).
-## Organizational level (00-01)
+## Organization (0 and 1)
-- [Bootstrap](00-bootstrap/README.md)
+- [Bootstrap](0-bootstrap/README.md)
Enables critical organization-level functionality that depends on broad permissions. It has two primary purposes. The first is to bootstrap the resources needed for automation of this and the following stages (service accounts, GCS buckets). And secondly, it applies the minimum amount of configuration needed at the organization level, to avoid the need of broad permissions later on, and to implement a minimum of security features like sinks and exports from the start.\
Exports: automation variables, organization-level custom roles
-- [Resource Management](01-resman/README.md)
+- [Resource Management](1-resman/README.md)
Creates the base resource hierarchy (folders) and the automation resources required later to delegate deployment of each part of the hierarchy to separate stages. This stage also configures organization-level policies and any exceptions needed by different branches of the resource hierarchy.\
Exports: folder ids, automation service account emails
-## Shared resources (02)
+## Multitenancy
-- [Security](02-security/README.md)
+Implemented via separate stages that configure separate FAST-enabled hierarchies for each tenant, check the [multitenant stages folder](../stages-multitenant/).
+
+## Shared resources (2)
+
+- [Security](2-security/README.md)
Manages centralized security configurations in a separate stage, and is typically owned by the security team. This stage implements VPC Security Controls via separate perimeters for environments and central services, and creates projects to host centralized KMS keys used by the whole organization. It's meant to be easily extended to include other security-related resources which are required, like Secret Manager.\
Exports: KMS key ids
-- Networking ([VPN](02-networking-vpn/README.md)/[NVA](02-networking-nva/README.md)/[Peering](02-networking-separate-envs/README.md)/[Separate environments](02-networking-separate-envs/README.md))
- Manages centralized network resources in a separate stage, and is typically owned by the networking team. This stage implements a hub-and-spoke design, and includes connectivity via VPN to on-premises, and YAML-based factories for firewall rules (hierarchical and VPC-level) and subnets. It's currently available in four flavors: [spokes connected via VPN](02-networking-vpn/README.md), [and spokes connected via appliances](02-networking-nva/README.md), [spokes connected via VPC peering](02-networking-peering/README.md), and [separated network environments](02-networking-separate-envs/README.md).\
+- Networking ([Peering](2-networking-a-peering/README.md)/[VPN](2-networking-b-vpn/README.md)/[NVA](2-networking-c-nva/README.md)/[Separate environments](2-networking-d-separate-envs/README.md))
+ Manages centralized network resources in a separate stage, and is typically owned by the networking team. This stage implements a hub-and-spoke design, and includes connectivity via VPN to on-premises, and YAML-based factories for firewall rules (hierarchical and VPC-level) and subnets. It's currently available in four flavors: [spokes connected via VPC peering](2-networking-a-peering/README.md), [spokes connected via VPN](2-networking-b-vpn/README.md), [and spokes connected via appliances](2-networking-c-nva/README.md), and [separated network environments](2-networking-d-separate-envs/README.md).\
Exports: host project ids and numbers, vpc self links
-## Environment-level resources (03)
+## Environment-level resources (3)
-- [Project Factory](03-project-factory/dev/)
+- [Project Factory](3-project-factory/dev/)
YAML-based fatory to create and configure application or team-level projects. Configuration includes VPC-level settings for Shared VPC, service-level configuration for CMEK encryption via centralized keys, and service account creation for workloads and applications. This stage is meant to be used once per environment.
-- [Data Platform](03-data-platform/dev/)
-- [GKE Multitenant](03-gke-multitenant/dev/)
+- [Data Platform](3-data-platform/dev/)
+- [GKE Multitenant](3-gke-multitenant/dev/)
- GCE Migration (in development)
diff --git a/modules/organization/README.md b/modules/organization/README.md
index da44046a9d..b6caa3cd03 100644
--- a/modules/organization/README.md
+++ b/modules/organization/README.md
@@ -462,13 +462,13 @@ module "org" {
| [iam_bindings_authoritative](variables.tf#L116) | IAM authoritative bindings, in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared. Bindings should also be authoritative when using authoritative audit config. Use with caution. | map(list(string))
| | null
|
| [logging_exclusions](variables.tf#L122) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string)
| | {}
|
| [logging_sinks](variables.tf#L129) | Logging sinks to create for the organization. | map(object({…}))
| | {}
|
-| [network_tags](variables.tf#L159) | Network tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…}))
| | {}
|
-| [org_policies](variables.tf#L180) | Organization policies applied to this organization keyed by policy name. | map(object({…}))
| | {}
|
+| [network_tags](variables.tf#L159) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…}))
| | {}
|
+| [org_policies](variables.tf#L181) | Organization policies applied to this organization keyed by policy name. | map(object({…}))
| | {}
|
| [org_policies_data_path](variables.tf#L220) | Path containing org policies in YAML format. | string
| | null
|
| [org_policy_custom_constraints](variables.tf#L226) | Organization policiy custom constraints keyed by constraint name. | map(object({…}))
| | {}
|
| [org_policy_custom_constraints_data_path](variables.tf#L240) | Path containing org policy custom constraints in YAML format. | string
| | null
|
| [tag_bindings](variables.tf#L255) | Tag bindings for this organization, in key => tag value id format. | map(string)
| | null
|
-| [tags](variables.tf#L261) | Tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…}))
| | {}
|
+| [tags](variables.tf#L261) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…}))
| | {}
|
## Outputs
@@ -480,9 +480,9 @@ module "org" {
| [firewall_policy_id](outputs.tf#L40) | Map of firewall policy ids created in the organization. | |
| [network_tag_keys](outputs.tf#L45) | Tag key resources. | |
| [network_tag_values](outputs.tf#L54) | Tag value resources. | |
-| [organization_id](outputs.tf#L65) | Organization id dependent on module resources. | |
-| [sink_writer_identities](outputs.tf#L82) | Writer identities created for each sink. | |
-| [tag_keys](outputs.tf#L90) | Tag key resources. | |
-| [tag_values](outputs.tf#L99) | Tag value resources. | |
+| [organization_id](outputs.tf#L62) | Organization id dependent on module resources. | |
+| [sink_writer_identities](outputs.tf#L79) | Writer identities created for each sink. | |
+| [tag_keys](outputs.tf#L87) | Tag key resources. | |
+| [tag_values](outputs.tf#L96) | Tag value resources. | |
diff --git a/modules/organization/outputs.tf b/modules/organization/outputs.tf
index 40d84b473a..2e594ee665 100644
--- a/modules/organization/outputs.tf
+++ b/modules/organization/outputs.tf
@@ -54,11 +54,8 @@ output "network_tag_keys" {
output "network_tag_values" {
description = "Tag value resources."
value = {
- for k, v in google_tags_tag_value.default
- : k => v if(
- google_tags_tag_key.default[split("/", k)[0]].purpose != null &&
- google_tags_tag_key.default[split("/", k)[0]].purpose != ""
- )
+ for k, v in google_tags_tag_value.default :
+ k => v if local.tag_values[k].tag_network
}
}
@@ -99,10 +96,7 @@ output "tag_keys" {
output "tag_values" {
description = "Tag value resources."
value = {
- for k, v in google_tags_tag_value.default
- : k => v if(
- google_tags_tag_key.default[split("/", k)[0]].purpose == null ||
- google_tags_tag_key.default[split("/", k)[0]].purpose == ""
- )
+ for k, v in google_tags_tag_value.default :
+ k => v if !local.tag_values[k].tag_network
}
}
diff --git a/modules/organization/tags.tf b/modules/organization/tags.tf
index 544b8989fc..b579479edc 100644
--- a/modules/organization/tags.tf
+++ b/modules/organization/tags.tf
@@ -23,17 +23,21 @@ locals {
"Managed by the Terraform organization module."
)
key = "${tag}/${value}"
+ id = try(value_attrs.id, null)
name = value
roles = keys(coalesce(
value_attrs == null ? null : value_attrs.iam, {}
))
- tag = tag
+ tag = tag
+ tag_id = attrs.id
+ tag_network = try(attrs.network, null) != null
}
]
])
_tag_values_iam = flatten([
for key, value_attrs in local.tag_values : [
for role in value_attrs.roles : {
+ id = value_attrs.id
key = value_attrs.key
name = value_attrs.name
role = role
@@ -44,8 +48,9 @@ locals {
_tags_iam = flatten([
for tag, attrs in local.tags : [
for role in keys(coalesce(attrs.iam, {})) : {
- role = role
- tag = tag
+ role = role
+ tag = tag
+ tag_id = attrs.id
}
]
])
@@ -64,7 +69,7 @@ locals {
# keys
resource "google_tags_tag_key" "default" {
- for_each = local.tags
+ for_each = { for k, v in local.tags : k => v if v.id == null }
parent = var.organization_id
purpose = (
lookup(each.value, "network", null) == null ? null : "GCE_FIREWALL"
@@ -83,8 +88,12 @@ resource "google_tags_tag_key" "default" {
resource "google_tags_tag_key_iam_binding" "default" {
for_each = local.tags_iam
- tag_key = google_tags_tag_key.default[each.value.tag].id
- role = each.value.role
+ tag_key = (
+ each.value.tag_id == null
+ ? google_tags_tag_key.default[each.value.tag].id
+ : each.value.tag_id
+ )
+ role = each.value.role
members = coalesce(
local.tags[each.value.tag]["iam"][each.value.role], []
)
@@ -93,16 +102,24 @@ resource "google_tags_tag_key_iam_binding" "default" {
# values
resource "google_tags_tag_value" "default" {
- for_each = local.tag_values
- parent = google_tags_tag_key.default[each.value.tag].id
+ for_each = { for k, v in local.tag_values : k => v if v.id == null }
+ parent = (
+ each.value.tag_id == null
+ ? google_tags_tag_key.default[each.value.tag].id
+ : each.value.tag_id
+ )
short_name = each.value.name
description = each.value.description
}
resource "google_tags_tag_value_iam_binding" "default" {
- for_each = local.tag_values_iam
- tag_value = google_tags_tag_value.default[each.value.key].id
- role = each.value.role
+ for_each = local.tag_values_iam
+ tag_value = (
+ each.value.id == null
+ ? google_tags_tag_value.default[each.value.key].id
+ : each.value.id
+ )
+ role = each.value.role
members = coalesce(
local.tags[each.value.tag]["values"][each.value.name]["iam"][each.value.role],
[]
diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf
index 84c81ff5b5..ced5cad3d2 100644
--- a/modules/organization/variables.tf
+++ b/modules/organization/variables.tf
@@ -157,10 +157,11 @@ variable "logging_sinks" {
}
variable "network_tags" {
- description = "Network tags by key name. The `iam` attribute behaves like the similarly named one at module level."
+ description = "Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level."
type = map(object({
description = optional(string, "Managed by the Terraform organization module.")
iam = optional(map(list(string)), {})
+ id = optional(string)
network = string # project_id/vpc_name
values = optional(map(object({
description = optional(string, "Managed by the Terraform organization module.")
@@ -193,7 +194,6 @@ variable "org_policies" {
values = optional(list(string))
}))
enforce = optional(bool, true) # for boolean policies only.
-
# conditional values
rules = optional(list(object({
allow = optional(object({
@@ -259,13 +259,15 @@ variable "tag_bindings" {
}
variable "tags" {
- description = "Tags by key name. The `iam` attribute behaves like the similarly named one at module level."
+ description = "Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level."
type = map(object({
description = optional(string, "Managed by the Terraform organization module.")
iam = optional(map(list(string)), {})
+ id = optional(string)
values = optional(map(object({
description = optional(string, "Managed by the Terraform organization module.")
iam = optional(map(list(string)), {})
+ id = optional(string)
})), {})
}))
nullable = false
diff --git a/tests/fast/stages/s03_data_platform/test_plan.py b/tests/fast/stages/s03_data_platform/test_plan.py
deleted file mode 100644
index 0bb333e711..0000000000
--- a/tests/fast/stages/s03_data_platform/test_plan.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2022 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-def test_counts(plan_summary):
- "Test stage."
- summary = plan_summary("fast/stages/03-data-platform/dev/",
- tf_var_files=["common.tfvars"])
- assert summary.counts["modules"] > 0
- assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s03_gke_multitenant/test_plan.py b/tests/fast/stages/s03_gke_multitenant/test_plan.py
deleted file mode 100644
index 2d196ec46b..0000000000
--- a/tests/fast/stages/s03_gke_multitenant/test_plan.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2022 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-def test_counts(plan_summary):
- "Test stage."
- summary = plan_summary("fast/stages/03-gke-multitenant/dev/",
- tf_var_files=["common.tfvars"])
- assert summary.counts["modules"] > 0
- assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s03_project_factory/common.tfvars b/tests/fast/stages/s03_project_factory/common.tfvars
deleted file mode 100644
index b65956b6b8..0000000000
--- a/tests/fast/stages/s03_project_factory/common.tfvars
+++ /dev/null
@@ -1,11 +0,0 @@
-data_dir = "../../../../tests/fast/stages/s03_project_factory/data/projects/"
-defaults_file = "../../../../tests/fast/stages/s03_project_factory/data/defaults.yaml"
-prefix = "test"
-environment_dns_zone = "dev"
-billing_account = {
- id = "000000-111111-222222"
- organization_id = 123456789012
-}
-vpc_self_links = {
- dev-spoke-0 = "link"
-}
diff --git a/tests/fast/stages/s03_project_factory/test_plan.py b/tests/fast/stages/s03_project_factory/test_plan.py
deleted file mode 100644
index 3b284abbfc..0000000000
--- a/tests/fast/stages/s03_project_factory/test_plan.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2022 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-def test_counts(plan_summary):
- "Test stage."
- summary = plan_summary("fast/stages/03-project-factory/dev",
- tf_var_files=["common.tfvars"])
- assert summary.counts["modules"] > 0
- assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s00_bootstrap/__init__.py b/tests/fast/stages/s0_bootstrap/__init__.py
similarity index 100%
rename from tests/fast/stages/s00_bootstrap/__init__.py
rename to tests/fast/stages/s0_bootstrap/__init__.py
diff --git a/tests/fast/stages/s00_bootstrap/simple.tfvars b/tests/fast/stages/s0_bootstrap/simple.tfvars
similarity index 71%
rename from tests/fast/stages/s00_bootstrap/simple.tfvars
rename to tests/fast/stages/s0_bootstrap/simple.tfvars
index f8ef5735bd..5c389f53ad 100644
--- a/tests/fast/stages/s00_bootstrap/simple.tfvars
+++ b/tests/fast/stages/s0_bootstrap/simple.tfvars
@@ -4,8 +4,7 @@ organization = {
customer_id = "C00000000"
}
billing_account = {
- id = "000000-111111-222222"
- organization_id = 123456789012
+ id = "000000-111111-222222"
}
prefix = "fast"
outputs_location = "/fast-config"
diff --git a/tests/fast/stages/s00_bootstrap/simple.yaml b/tests/fast/stages/s0_bootstrap/simple.yaml
similarity index 81%
rename from tests/fast/stages/s00_bootstrap/simple.yaml
rename to tests/fast/stages/s0_bootstrap/simple.yaml
index ed0d773808..4b65e836d5 100644
--- a/tests/fast/stages/s00_bootstrap/simple.yaml
+++ b/tests/fast/stages/s0_bootstrap/simple.yaml
@@ -13,8 +13,7 @@
# limitations under the License.
counts:
- google_bigquery_dataset: 2
- google_bigquery_dataset_iam_member: 2
+ google_bigquery_dataset: 1
google_bigquery_default_service_account: 3
google_logging_organization_sink: 2
google_organization_iam_binding: 19
@@ -22,14 +21,14 @@ counts:
google_organization_iam_member: 16
google_project: 3
google_project_iam_binding: 9
- google_project_iam_member: 1
+ google_project_iam_member: 3
google_project_service: 29
google_project_service_identity: 3
- google_service_account: 3
- google_service_account_iam_binding: 3
- google_storage_bucket: 4
- google_storage_bucket_iam_binding: 2
- google_storage_bucket_iam_member: 3
+ google_service_account: 2
+ google_service_account_iam_binding: 1
+ google_storage_bucket: 3
+ google_storage_bucket_iam_binding: 1
+ google_storage_bucket_iam_member: 2
google_storage_bucket_object: 5
google_storage_project_service_account: 3
local_file: 5
@@ -45,5 +44,4 @@ outputs:
log-export: fast-prod-audit-logs-0
service_accounts:
bootstrap: fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com
- cicd: fast-prod-cicd-0@fast-prod-iac-core-0.iam.gserviceaccount.com
resman: fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com
diff --git a/tests/fast/stages/s00_bootstrap/simple_projects.yaml b/tests/fast/stages/s0_bootstrap/simple_projects.yaml
similarity index 100%
rename from tests/fast/stages/s00_bootstrap/simple_projects.yaml
rename to tests/fast/stages/s0_bootstrap/simple_projects.yaml
diff --git a/tests/fast/stages/s00_bootstrap/simple_sas.yaml b/tests/fast/stages/s0_bootstrap/simple_sas.yaml
similarity index 82%
rename from tests/fast/stages/s00_bootstrap/simple_sas.yaml
rename to tests/fast/stages/s0_bootstrap/simple_sas.yaml
index ba84948d86..0424e5983f 100644
--- a/tests/fast/stages/s00_bootstrap/simple_sas.yaml
+++ b/tests/fast/stages/s0_bootstrap/simple_sas.yaml
@@ -17,10 +17,6 @@ values:
account_id: fast-prod-bootstrap-0
display_name: Terraform organization bootstrap service account.
project: fast-prod-iac-core-0
- module.automation-tf-cicd-provisioning-sa.google_service_account.service_account[0]:
- account_id: fast-prod-cicd-0
- display_name: Terraform stage 1 CICD service account.
- project: fast-prod-iac-core-0
module.automation-tf-resman-sa.google_service_account.service_account[0]:
account_id: fast-prod-resman-0
display_name: Terraform stage 1 resman service account.
diff --git a/tests/fast/stages/s00_bootstrap/tftest.yaml b/tests/fast/stages/s0_bootstrap/tftest.yaml
similarity index 83%
rename from tests/fast/stages/s00_bootstrap/tftest.yaml
rename to tests/fast/stages/s0_bootstrap/tftest.yaml
index 4656859bc2..b53749adcc 100644
--- a/tests/fast/stages/s00_bootstrap/tftest.yaml
+++ b/tests/fast/stages/s0_bootstrap/tftest.yaml
@@ -1,6 +1,6 @@
# skip boilerplate check
-module: fast/stages/00-bootstrap
+module: fast/stages/0-bootstrap
tests:
simple:
diff --git a/tests/fast/stages/s01_resman/__init__.py b/tests/fast/stages/s1_resman/__init__.py
similarity index 100%
rename from tests/fast/stages/s01_resman/__init__.py
rename to tests/fast/stages/s1_resman/__init__.py
diff --git a/tests/fast/stages/s01_resman/common.tfvars b/tests/fast/stages/s1_resman/common.tfvars
similarity index 91%
rename from tests/fast/stages/s01_resman/common.tfvars
rename to tests/fast/stages/s1_resman/common.tfvars
index f6d1d5acf4..34c61351e6 100644
--- a/tests/fast/stages/s01_resman/common.tfvars
+++ b/tests/fast/stages/s1_resman/common.tfvars
@@ -6,8 +6,7 @@ automation = {
outputs_bucket = "test"
}
billing_account = {
- id = "000000-111111-222222"
- organization_id = 123456789012
+ id = "000000-111111-222222"
}
custom_roles = {
# organization_iam_admin = "organizations/123456789012/roles/organizationIamAdmin",
diff --git a/tests/fast/stages/s01_resman/test_plan.py b/tests/fast/stages/s1_resman/test_plan.py
similarity index 93%
rename from tests/fast/stages/s01_resman/test_plan.py
rename to tests/fast/stages/s1_resman/test_plan.py
index c8dce75082..39bdfc11ee 100644
--- a/tests/fast/stages/s01_resman/test_plan.py
+++ b/tests/fast/stages/s1_resman/test_plan.py
@@ -15,7 +15,7 @@
def test_counts(plan_summary):
"Test stage."
- summary = plan_summary("fast/stages/01-resman",
+ summary = plan_summary("fast/stages/1-resman",
tf_var_files=["common.tfvars"])
assert summary.counts["modules"] > 0
assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s02_networking_nva/__init__.py b/tests/fast/stages/s2_networking_a_peering/__init__.py
similarity index 100%
rename from tests/fast/stages/s02_networking_nva/__init__.py
rename to tests/fast/stages/s2_networking_a_peering/__init__.py
diff --git a/tests/fast/stages/s02_networking_peering/common.tfvars b/tests/fast/stages/s2_networking_a_peering/common.tfvars
similarity index 83%
rename from tests/fast/stages/s02_networking_peering/common.tfvars
rename to tests/fast/stages/s2_networking_a_peering/common.tfvars
index 11b49d7c04..d3e0779fc3 100644
--- a/tests/fast/stages/s02_networking_peering/common.tfvars
+++ b/tests/fast/stages/s2_networking_a_peering/common.tfvars
@@ -1,10 +1,9 @@
-data_dir = "../../../fast/stages/02-networking-peering/data/"
+data_dir = "../../../fast/stages/2-networking-a-peering/data/"
automation = {
outputs_bucket = "test"
}
billing_account = {
- id = "000000-111111-222222"
- organization_id = 123456789012
+ id = "000000-111111-222222"
}
custom_roles = {
service_project_network_admin = "organizations/123456789012/roles/foo"
diff --git a/tests/fast/stages/s02_networking_peering/test_plan.py b/tests/fast/stages/s2_networking_a_peering/test_plan.py
similarity index 87%
rename from tests/fast/stages/s02_networking_peering/test_plan.py
rename to tests/fast/stages/s2_networking_a_peering/test_plan.py
index dff0fd47b6..09590d361d 100644
--- a/tests/fast/stages/s02_networking_peering/test_plan.py
+++ b/tests/fast/stages/s2_networking_a_peering/test_plan.py
@@ -20,16 +20,16 @@
BASEDIR = Path(__file__).parent
FIXTURE_PEERING = BASEDIR / 'fixture'
-FIXTURE_VPN = BASEDIR.parent / 's02_networking_vpn/fixture'
+FIXTURE_VPN = BASEDIR.parent / 's2_networking_b_vpn/fixture'
STAGES = Path(__file__).parents[4] / 'fast/stages'
-STAGE_PEERING = STAGES / '02-networking-peering'
-STAGE_VPN = STAGES / '02-networking-vpn'
+STAGE_PEERING = STAGES / '2-networking-a-peering'
+STAGE_VPN = STAGES / '2-networking-b-vpn'
def test_counts(plan_summary):
"Test stage."
- summary = plan_summary("fast/stages/02-networking-peering",
+ summary = plan_summary("fast/stages/2-networking-a-peering",
tf_var_files=["common.tfvars"])
assert summary.counts["modules"] > 0
assert summary.counts["resources"] > 0
@@ -38,9 +38,9 @@ def test_counts(plan_summary):
def test_vpn_peering_parity(plan_summary):
'''Ensure VPN- and peering-based networking stages are identical except
for VPN and VPC peering resources'''
- summary_peering = plan_summary("fast/stages/02-networking-peering",
+ summary_peering = plan_summary("fast/stages/2-networking-a-peering",
tf_var_files=["common.tfvars"])
- summary_vpn = plan_summary("fast/stages/02-networking-vpn",
+ summary_vpn = plan_summary("fast/stages/2-networking-b-vpn",
tf_var_files=["common.tfvars"])
ddiff = DeepDiff(summary_vpn.values, summary_peering.values,
diff --git a/tests/fast/stages/s02_networking_peering/__init__.py b/tests/fast/stages/s2_networking_b_vpn/__init__.py
similarity index 100%
rename from tests/fast/stages/s02_networking_peering/__init__.py
rename to tests/fast/stages/s2_networking_b_vpn/__init__.py
diff --git a/tests/fast/stages/s02_networking_vpn/common.tfvars b/tests/fast/stages/s2_networking_b_vpn/common.tfvars
similarity index 83%
rename from tests/fast/stages/s02_networking_vpn/common.tfvars
rename to tests/fast/stages/s2_networking_b_vpn/common.tfvars
index 7241594d1c..66a7a60909 100644
--- a/tests/fast/stages/s02_networking_vpn/common.tfvars
+++ b/tests/fast/stages/s2_networking_b_vpn/common.tfvars
@@ -1,10 +1,9 @@
-data_dir = "../../../../../fast/stages/02-networking-vpn/data/"
+data_dir = "../../../../../fast/stages/2-networking-b-vpn/data/"
automation = {
outputs_bucket = "test"
}
billing_account = {
- id = "000000-111111-222222"
- organization_id = 123456789012
+ id = "000000-111111-222222"
}
custom_roles = {
service_project_network_admin = "organizations/123456789012/roles/foo"
diff --git a/tests/fast/stages/s02_networking_vpn/fixture/main.tf b/tests/fast/stages/s2_networking_b_vpn/fixture/main.tf
similarity index 100%
rename from tests/fast/stages/s02_networking_vpn/fixture/main.tf
rename to tests/fast/stages/s2_networking_b_vpn/fixture/main.tf
diff --git a/tests/fast/stages/s02_networking_nva/test_plan.py b/tests/fast/stages/s2_networking_b_vpn/test_plan.py
similarity index 92%
rename from tests/fast/stages/s02_networking_nva/test_plan.py
rename to tests/fast/stages/s2_networking_b_vpn/test_plan.py
index 24964f7abe..8ac1bade10 100644
--- a/tests/fast/stages/s02_networking_nva/test_plan.py
+++ b/tests/fast/stages/s2_networking_b_vpn/test_plan.py
@@ -15,7 +15,7 @@
def test_counts(plan_summary):
"Test stage."
- summary = plan_summary("fast/stages/02-networking-nva",
+ summary = plan_summary("fast/stages/2-networking-b-vpn",
tf_var_files=["common.tfvars"])
assert summary.counts["modules"] > 0
assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s02_networking_separate_envs/__init__.py b/tests/fast/stages/s2_networking_c_nva/__init__.py
similarity index 100%
rename from tests/fast/stages/s02_networking_separate_envs/__init__.py
rename to tests/fast/stages/s2_networking_c_nva/__init__.py
diff --git a/tests/fast/stages/s02_networking_nva/common.tfvars b/tests/fast/stages/s2_networking_c_nva/common.tfvars
similarity index 81%
rename from tests/fast/stages/s02_networking_nva/common.tfvars
rename to tests/fast/stages/s2_networking_c_nva/common.tfvars
index acfc641f33..ad12b8d339 100644
--- a/tests/fast/stages/s02_networking_nva/common.tfvars
+++ b/tests/fast/stages/s2_networking_c_nva/common.tfvars
@@ -1,10 +1,9 @@
-data_dir = "../../../fast/stages/02-networking-nva/data/"
+data_dir = "../../../fast/stages/2-networking-c-nva/data/"
automation = {
outputs_bucket = "test"
}
billing_account = {
- id = "000000-111111-222222"
- organization_id = 123456789012
+ id = "000000-111111-222222"
}
custom_roles = {
service_project_network_admin = "organizations/123456789012/roles/foo"
diff --git a/tests/fast/stages/s02_networking_vpn/test_plan.py b/tests/fast/stages/s2_networking_c_nva/test_plan.py
similarity index 92%
rename from tests/fast/stages/s02_networking_vpn/test_plan.py
rename to tests/fast/stages/s2_networking_c_nva/test_plan.py
index cc62dbf736..70c37bc388 100644
--- a/tests/fast/stages/s02_networking_vpn/test_plan.py
+++ b/tests/fast/stages/s2_networking_c_nva/test_plan.py
@@ -15,7 +15,7 @@
def test_counts(plan_summary):
"Test stage."
- summary = plan_summary("fast/stages/02-networking-vpn",
+ summary = plan_summary("fast/stages/2-networking-c-nva",
tf_var_files=["common.tfvars"])
assert summary.counts["modules"] > 0
assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s02_networking_vpn/__init__.py b/tests/fast/stages/s2_networking_d_separate_envs/__init__.py
similarity index 100%
rename from tests/fast/stages/s02_networking_vpn/__init__.py
rename to tests/fast/stages/s2_networking_d_separate_envs/__init__.py
diff --git a/tests/fast/stages/s02_networking_separate_envs/common.tfvars b/tests/fast/stages/s2_networking_d_separate_envs/common.tfvars
similarity index 78%
rename from tests/fast/stages/s02_networking_separate_envs/common.tfvars
rename to tests/fast/stages/s2_networking_d_separate_envs/common.tfvars
index c6b793fd1d..3ff0020ac2 100644
--- a/tests/fast/stages/s02_networking_separate_envs/common.tfvars
+++ b/tests/fast/stages/s2_networking_d_separate_envs/common.tfvars
@@ -1,10 +1,9 @@
-data_dir = "../../../../../fast/stages/02-networking-separate-envs/data/"
+data_dir = "../../../../../fast/stages/2-networking-d-separate-envs/data/"
automation = {
outputs_bucket = "test"
}
billing_account = {
- id = "000000-111111-222222"
- organization_id = 123456789012
+ id = "000000-111111-222222"
}
custom_roles = {
service_project_network_admin = "organizations/123456789012/roles/foo"
diff --git a/tests/fast/stages/s02_networking_separate_envs/test_plan.py b/tests/fast/stages/s2_networking_d_separate_envs/test_plan.py
similarity index 91%
rename from tests/fast/stages/s02_networking_separate_envs/test_plan.py
rename to tests/fast/stages/s2_networking_d_separate_envs/test_plan.py
index 89f6e25743..51a9257af8 100644
--- a/tests/fast/stages/s02_networking_separate_envs/test_plan.py
+++ b/tests/fast/stages/s2_networking_d_separate_envs/test_plan.py
@@ -15,7 +15,7 @@
def test_counts(plan_summary):
"Test stage."
- summary = plan_summary("fast/stages/02-networking-separate-envs",
+ summary = plan_summary("fast/stages/2-networking-d-separate-envs",
tf_var_files=["common.tfvars"])
assert summary.counts["modules"] > 0
assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s02_security/__init__.py b/tests/fast/stages/s2_security/__init__.py
similarity index 100%
rename from tests/fast/stages/s02_security/__init__.py
rename to tests/fast/stages/s2_security/__init__.py
diff --git a/tests/fast/stages/s02_security/common.tfvars b/tests/fast/stages/s2_security/common.tfvars
similarity index 91%
rename from tests/fast/stages/s02_security/common.tfvars
rename to tests/fast/stages/s2_security/common.tfvars
index b480a67320..6fbb60b645 100644
--- a/tests/fast/stages/s02_security/common.tfvars
+++ b/tests/fast/stages/s2_security/common.tfvars
@@ -2,16 +2,15 @@ automation = {
outputs_bucket = "test"
}
billing_account = {
- id = "000000-111111-222222"
- organization_id = 123456789012
+ id = "000000-111111-222222"
}
folder_ids = {
security = null
}
organization = {
- domain = "gcp-pso-italy.net"
- id = 856933387836
- customer_id = "C01lmug8b"
+ domain = "fast.example.com"
+ id = 123456789012
+ customer_id = "C00000000"
}
prefix = "fast"
kms_keys = {
diff --git a/tests/fast/stages/s02_security/test_plan.py b/tests/fast/stages/s2_security/test_plan.py
similarity index 93%
rename from tests/fast/stages/s02_security/test_plan.py
rename to tests/fast/stages/s2_security/test_plan.py
index 004f6b8368..edf5622e7d 100644
--- a/tests/fast/stages/s02_security/test_plan.py
+++ b/tests/fast/stages/s2_security/test_plan.py
@@ -15,7 +15,7 @@
def test_counts(plan_summary):
"Test stage."
- summary = plan_summary("fast/stages/02-security",
+ summary = plan_summary("fast/stages/2-security",
tf_var_files=["common.tfvars"])
assert summary.counts["modules"] > 0
assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s03_data_platform/__init__.py b/tests/fast/stages/s3_data_platform/__init__.py
similarity index 100%
rename from tests/fast/stages/s03_data_platform/__init__.py
rename to tests/fast/stages/s3_data_platform/__init__.py
diff --git a/tests/fast/stages/s03_data_platform/common.tfvars b/tests/fast/stages/s3_data_platform/common.tfvars
similarity index 85%
rename from tests/fast/stages/s03_data_platform/common.tfvars
rename to tests/fast/stages/s3_data_platform/common.tfvars
index f5aada165d..2ec41d37ad 100644
--- a/tests/fast/stages/s03_data_platform/common.tfvars
+++ b/tests/fast/stages/s3_data_platform/common.tfvars
@@ -2,8 +2,7 @@ automation = {
outputs_bucket = "test"
}
billing_account = {
- id = "012345-67890A-BCDEF0",
- organization_id = 123456
+ id = "012345-67890A-BCDEF0",
}
folder_ids = {
data-platform-dev = "folders/12345678"
@@ -12,9 +11,9 @@ host_project_ids = {
dev-spoke-0 = "fast-dev-net-spoke-0"
}
organization = {
- domain = "example.com"
+ domain = "fast.example.com"
id = 123456789012
- customer_id = "A11aaaaa1"
+ customer_id = "C00000000"
}
prefix = "fast"
subnet_self_links = {
diff --git a/tests/fast/stages/s3_data_platform/test_plan.py b/tests/fast/stages/s3_data_platform/test_plan.py
new file mode 100644
index 0000000000..ad7fa3d28a
--- /dev/null
+++ b/tests/fast/stages/s3_data_platform/test_plan.py
@@ -0,0 +1,21 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def test_counts(plan_summary):
+ "Test stage."
+ summary = plan_summary("fast/stages/3-data-platform/dev/",
+ tf_var_files=["common.tfvars"])
+ assert summary.counts["modules"] > 0
+ assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s03_gke_multitenant/__init__.py b/tests/fast/stages/s3_gke_multitenant/__init__.py
similarity index 100%
rename from tests/fast/stages/s03_gke_multitenant/__init__.py
rename to tests/fast/stages/s3_gke_multitenant/__init__.py
diff --git a/tests/fast/stages/s03_gke_multitenant/common.tfvars b/tests/fast/stages/s3_gke_multitenant/common.tfvars
similarity index 92%
rename from tests/fast/stages/s03_gke_multitenant/common.tfvars
rename to tests/fast/stages/s3_gke_multitenant/common.tfvars
index d22db1d265..1cafdd9aba 100644
--- a/tests/fast/stages/s03_gke_multitenant/common.tfvars
+++ b/tests/fast/stages/s3_gke_multitenant/common.tfvars
@@ -2,8 +2,7 @@ automation = {
outputs_bucket = "test"
}
billing_account = {
- id = "012345-67890A-BCDEF0",
- organization_id = 123456
+ id = "012345-67890A-BCDEF0",
}
clusters = {
mycluster = {
diff --git a/tests/fast/stages/s3_gke_multitenant/test_plan.py b/tests/fast/stages/s3_gke_multitenant/test_plan.py
new file mode 100644
index 0000000000..c517cb9338
--- /dev/null
+++ b/tests/fast/stages/s3_gke_multitenant/test_plan.py
@@ -0,0 +1,21 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def test_counts(plan_summary):
+ "Test stage."
+ summary = plan_summary("fast/stages/3-gke-multitenant/dev/",
+ tf_var_files=["common.tfvars"])
+ assert summary.counts["modules"] > 0
+ assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages/s03_project_factory/__init__.py b/tests/fast/stages/s3_project_factory/__init__.py
similarity index 100%
rename from tests/fast/stages/s03_project_factory/__init__.py
rename to tests/fast/stages/s3_project_factory/__init__.py
diff --git a/tests/fast/stages/s3_project_factory/common.tfvars b/tests/fast/stages/s3_project_factory/common.tfvars
new file mode 100644
index 0000000000..d3f8c6f9ad
--- /dev/null
+++ b/tests/fast/stages/s3_project_factory/common.tfvars
@@ -0,0 +1,10 @@
+data_dir = "../../../../tests/fast/stages/s3_project_factory/data/projects/"
+defaults_file = "../../../../tests/fast/stages/s3_project_factory/data/defaults.yaml"
+prefix = "test"
+environment_dns_zone = "dev"
+billing_account = {
+ id = "000000-111111-222222"
+}
+vpc_self_links = {
+ dev-spoke-0 = "link"
+}
diff --git a/tests/fast/stages/s03_project_factory/data/defaults.yaml b/tests/fast/stages/s3_project_factory/data/defaults.yaml
similarity index 100%
rename from tests/fast/stages/s03_project_factory/data/defaults.yaml
rename to tests/fast/stages/s3_project_factory/data/defaults.yaml
diff --git a/tests/fast/stages/s03_project_factory/data/projects/project.yaml b/tests/fast/stages/s3_project_factory/data/projects/project.yaml
similarity index 100%
rename from tests/fast/stages/s03_project_factory/data/projects/project.yaml
rename to tests/fast/stages/s3_project_factory/data/projects/project.yaml
diff --git a/tests/fast/stages/s3_project_factory/test_plan.py b/tests/fast/stages/s3_project_factory/test_plan.py
new file mode 100644
index 0000000000..fa293da849
--- /dev/null
+++ b/tests/fast/stages/s3_project_factory/test_plan.py
@@ -0,0 +1,21 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def test_counts(plan_summary):
+ "Test stage."
+ summary = plan_summary("fast/stages/3-project-factory/dev",
+ tf_var_files=["common.tfvars"])
+ assert summary.counts["modules"] > 0
+ assert summary.counts["resources"] > 0
diff --git a/tests/fast/stages_multitenant/__init__.py b/tests/fast/stages_multitenant/__init__.py
new file mode 100644
index 0000000000..6d6d1266c3
--- /dev/null
+++ b/tests/fast/stages_multitenant/__init__.py
@@ -0,0 +1,13 @@
+# 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.
diff --git a/tests/fast/stages_multitenant/s0_bootstrap_tenant/__init__.py b/tests/fast/stages_multitenant/s0_bootstrap_tenant/__init__.py
new file mode 100644
index 0000000000..6d6d1266c3
--- /dev/null
+++ b/tests/fast/stages_multitenant/s0_bootstrap_tenant/__init__.py
@@ -0,0 +1,13 @@
+# 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.
diff --git a/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.tfvars b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.tfvars
new file mode 100644
index 0000000000..f77e541152
--- /dev/null
+++ b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.tfvars
@@ -0,0 +1,60 @@
+automation = {
+ federated_identity_pool = null
+ federated_identity_providers = null
+ project_id = "fast-prod-automation"
+ project_number = 123456
+ outputs_bucket = "test"
+}
+billing_account = {
+ id = "000000-111111-222222"
+}
+custom_roles = {
+ # organization_iam_admin = "organizations/123456789012/roles/organizationIamAdmin",
+ service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin"
+}
+groups = {
+ gcp-billing-admins = "gcp-billing-admins",
+ gcp-devops = "gcp-devops",
+ gcp-network-admins = "gcp-network-admins",
+ gcp-organization-admins = "gcp-organization-admins",
+ gcp-security-admins = "gcp-security-admins",
+ gcp-support = "gcp-support"
+}
+organization = {
+ domain = "fast.example.com"
+ id = 123456789012
+ customer_id = "C00000000"
+}
+prefix = "fast2"
+tag_keys = {
+ context = "tagKeys/1234567890"
+ environment = "tagKeys/4567890123"
+ tenant = "tagKeys/7890123456"
+}
+tag_names = {
+ context = "context"
+ environment = "environment"
+ tenant = "tenant"
+}
+tag_values = {
+ "context/data" : "tagValues/1234567890",
+ "context/gke" : "tagValues/1234567890",
+ "context/networking" : "tagValues/1234567890",
+ "context/sandbox" : "tagValues/1234567890",
+ "context/security" : "tagValues/1234567890",
+ "context/teams" : "tagValues/1234567890",
+ "environment/development" : "tagValues/1234567890",
+ "environment/production" : "tagValues/1234567890"
+}
+tenant_config = {
+ groups = {
+ gcp-admins = "gcp-tn01-admins"
+ }
+ descriptive_name = "Tenant 01"
+ locations = {
+ gcs = "europe-west8"
+ logging = "europe-west8"
+ }
+ short_name = "tn01"
+}
+test_principal = "foo-prod-resman-0@foo-prod-iac-core-0.iam.gserviceaccount.com"
diff --git a/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.yaml b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.yaml
new file mode 100644
index 0000000000..7e132128c1
--- /dev/null
+++ b/tests/fast/stages_multitenant/s0_bootstrap_tenant/simple.yaml
@@ -0,0 +1,33 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+counts:
+ google_bigquery_default_service_account: 2
+ google_folder: 2
+ google_folder_iam_binding: 5
+ google_organization_iam_member: 38
+ google_project: 2
+ google_project_iam_binding: 8
+ google_project_service: 26
+ google_project_service_identity: 3
+ google_service_account: 11
+ google_storage_bucket: 2
+ google_storage_bucket_iam_binding: 1
+ google_storage_bucket_iam_member: 1
+ google_storage_bucket_object: 2
+ google_storage_project_service_account: 2
+ google_tags_tag_binding: 1
+ google_tags_tag_value: 1
+ modules: 19
+ resources: 128
diff --git a/tests/fast/stages_multitenant/s0_bootstrap_tenant/tftest.yaml b/tests/fast/stages_multitenant/s0_bootstrap_tenant/tftest.yaml
new file mode 100644
index 0000000000..c2fa9fa8e9
--- /dev/null
+++ b/tests/fast/stages_multitenant/s0_bootstrap_tenant/tftest.yaml
@@ -0,0 +1,10 @@
+# skip boilerplate check
+
+module: fast/stages-multitenant/0-bootstrap-tenant
+
+tests:
+ simple:
+ tfvars:
+ - simple.tfvars
+ inventory:
+ - simple.yaml
diff --git a/tests/fast/stages_multitenant/s1_resman_tenant/__init__.py b/tests/fast/stages_multitenant/s1_resman_tenant/__init__.py
new file mode 100644
index 0000000000..6d6d1266c3
--- /dev/null
+++ b/tests/fast/stages_multitenant/s1_resman_tenant/__init__.py
@@ -0,0 +1,13 @@
+# 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.
diff --git a/tests/fast/stages_multitenant/s1_resman_tenant/simple.tfvars b/tests/fast/stages_multitenant/s1_resman_tenant/simple.tfvars
new file mode 100644
index 0000000000..33cf461989
--- /dev/null
+++ b/tests/fast/stages_multitenant/s1_resman_tenant/simple.tfvars
@@ -0,0 +1,70 @@
+automation = {
+ federated_identity_pools = null
+ federated_identity_providers = null
+ project_id = "tn0-prod-automation-0"
+ project_number = 123456
+ outputs_bucket = "tn0-prod-automation-0"
+ service_accounts = {
+ networking = "foo-tn0-net-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ resman = "foo-tn0-resman-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ security = "foo-tn0-sec-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ dp-dev = "foo-tn0-dp-dev-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ dp-prod = "foo-tn0-dp-prod-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ gke-dev = "foo-tn0-gke-dev-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ gke-prod = "foo-tn0-gke-prod-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ pf-dev = "foo-tn0-pf-dev-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ pf-prod = "foo-tn0-pf-prod-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ sandbox = "foo-tn0-sandbox-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ teams = "foo-tn0-teams-0@foo-tn0-prod-iac-core-0.iam.gserviceaccount.com"
+ }
+}
+billing_account = {
+ id = "000000-111111-222222"
+}
+custom_roles = {
+ # organization_iam_admin = "organizations/123456789012/roles/organizationIamAdmin",
+ service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin"
+}
+fast_features = {
+ data_platform = true
+ gke = true
+ project_factory = true
+ sandbox = true
+ teams = true
+}
+groups = {
+ gcp-devops = "gcp-devops",
+ gcp-network-admins = "gcp-network-admins",
+ gcp-security-admins = "gcp-security-admins",
+}
+organization = {
+ domain = "fast.example.com"
+ id = 123456789012
+ customer_id = "C00000000"
+}
+prefix = "foo-tn0"
+root_node = "folders/1234567890"
+short_name = "tn0"
+tags = {
+ keys = {
+ context = "tagKeys/1234567890"
+ environment = "tagKeys/4567890123"
+ tenant = "tagKeys/7890123456"
+ }
+ names = {
+ context = "context"
+ environment = "environment"
+ tenant = "tenant"
+ }
+ values = {
+ "context/data" : "tagValues/1234567890",
+ "context/gke" : "tagValues/1234567890",
+ "context/networking" : "tagValues/1234567890",
+ "context/sandbox" : "tagValues/1234567890",
+ "context/security" : "tagValues/1234567890",
+ "context/teams" : "tagValues/1234567890",
+ "environment/development" : "tagValues/1234567890",
+ "environment/production" : "tagValues/1234567890"
+ }
+}
+test_skip_data_sources = true
diff --git a/tests/fast/stages_multitenant/s1_resman_tenant/simple.yaml b/tests/fast/stages_multitenant/s1_resman_tenant/simple.yaml
new file mode 100644
index 0000000000..44c07c62fb
--- /dev/null
+++ b/tests/fast/stages_multitenant/s1_resman_tenant/simple.yaml
@@ -0,0 +1,28 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+counts:
+ google_folder: 13
+ google_folder_iam_binding: 42
+ google_folder_iam_member: 3
+ google_org_policy_policy: 2
+ google_service_account: 9
+ google_service_account_iam_binding: 8
+ google_storage_bucket: 10
+ google_storage_bucket_iam_binding: 10
+ google_storage_bucket_iam_member: 9
+ google_storage_bucket_object: 11
+ google_tags_tag_binding: 12
+ modules: 32
+ resources: 129
diff --git a/tests/fast/stages_multitenant/s1_resman_tenant/tftest.yaml b/tests/fast/stages_multitenant/s1_resman_tenant/tftest.yaml
new file mode 100644
index 0000000000..2e107e08d2
--- /dev/null
+++ b/tests/fast/stages_multitenant/s1_resman_tenant/tftest.yaml
@@ -0,0 +1,10 @@
+# skip boilerplate check
+
+module: fast/stages-multitenant/1-resman-tenant
+
+tests:
+ simple:
+ tfvars:
+ - simple.tfvars
+ inventory:
+ - simple.yaml
diff --git a/tests/fixtures.py b/tests/fixtures.py
index 788f81786f..11a397de7e 100644
--- a/tests/fixtures.py
+++ b/tests/fixtures.py
@@ -31,7 +31,7 @@
@contextlib.contextmanager
def _prepare_root_module(path):
"""Context manager to prepare a terraform module to be tested.
-
+
If the TFTEST_COPY environment variable is set, `path` is copied to
a temporary directory and a few terraform files (e.g.
terraform.tfvars) are delete to ensure a clean test environment.
@@ -49,6 +49,7 @@ def _prepare_root_module(path):
# deployment with links to configs)
ignore_patterns = shutil.ignore_patterns('*.auto.tfvars',
'*.auto.tfvars.json',
+ '[0-9]-*-providers.tf',
'terraform.tfstate*',
'terraform.tfvars', '.terraform')
@@ -180,19 +181,19 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None,
expected_values = inventory['values']
for address, expected_value in expected_values.items():
assert address in summary.values, \
- f'{address} is not a valid address in the plan'
+ f'{address} is not a valid address in the plan'
for k, v in expected_value.items():
assert k in summary.values[address], \
- f'{k} not found at {address}'
+ f'{k} not found at {address}'
plan_value = summary.values[address][k]
assert plan_value == v, \
- f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`'
+ f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`'
if 'counts' in inventory:
expected_counts = inventory['counts']
for type_, expected_count in expected_counts.items():
assert type_ in summary.counts, \
- f'module does not create any resources of type `{type_}`'
+ f'module does not create any resources of type `{type_}`'
plan_count = summary.counts[type_]
assert plan_count == expected_count, \
f'count of {type_} resources failed. Got {plan_count}, expected {expected_count}'
@@ -201,7 +202,7 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None,
expected_outputs = inventory['outputs']
for output_name, expected_output in expected_outputs.items():
assert output_name in summary.outputs, \
- f'module does not output `{output_name}`'
+ f'module does not output `{output_name}`'
output = summary.outputs[output_name]
# assert 'value' in output, \
# f'output `{output_name}` does not have a value (is it sensitive or dynamic?)'
diff --git a/tests/modules/organization/tags.tfvars b/tests/modules/organization/tags.tfvars
new file mode 100644
index 0000000000..2a4dcb42f1
--- /dev/null
+++ b/tests/modules/organization/tags.tfvars
@@ -0,0 +1,59 @@
+network_tags = {
+ net_environment = {
+ network = "foobar"
+ }
+}
+tags = {
+ foo = {}
+ bar = {
+ description = null
+ iam = null
+ values = null
+ }
+ baz = {
+ id = "tagKeys/1234567890"
+ values = {
+ one = null
+ two = null
+ }
+ }
+ foobar = {
+ description = "Foobar tag."
+ iam = {
+ "roles/resourcemanager.tagAdmin" = [
+ "user:user1@example.com", "user:user2@example.com"
+ ]
+ }
+ values = {
+ one = null
+ two = {
+ description = "Foobar 2."
+ iam = {
+ "roles/resourcemanager.tagViewer" = [
+ "user:user3@example.com"
+ ]
+ }
+ }
+ three = {
+ description = "Foobar 3."
+ iam = {
+ "roles/resourcemanager.tagViewer" = [
+ "user:user3@example.com"
+ ]
+ "roles/resourcemanager.tagAdmin" = [
+ "user:user4@example.com"
+ ]
+ }
+ }
+ four = {
+ description = "Foobar 4."
+ id = "tagValues/1234567890"
+ iam = {
+ "roles/resourcemanager.tagViewer" = [
+ "user:user4@example.com"
+ ]
+ }
+ }
+ }
+ }
+}
diff --git a/tests/modules/organization/tags.yaml b/tests/modules/organization/tags.yaml
new file mode 100644
index 0000000000..3e5524d473
--- /dev/null
+++ b/tests/modules/organization/tags.yaml
@@ -0,0 +1,76 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ google_tags_tag_key.default["bar"]:
+ description: Managed by the Terraform organization module.
+ parent: organizations/1234567890
+ purpose: null
+ purpose_data: null
+ short_name: bar
+ google_tags_tag_key.default["foo"]:
+ description: Managed by the Terraform organization module.
+ parent: organizations/1234567890
+ purpose: null
+ purpose_data: null
+ short_name: foo
+ google_tags_tag_key.default["foobar"]:
+ description: Foobar tag.
+ parent: organizations/1234567890
+ purpose: null
+ purpose_data: null
+ short_name: foobar
+ google_tags_tag_key.default["net_environment"]:
+ description: Managed by the Terraform organization module.
+ parent: organizations/1234567890
+ purpose: GCE_FIREWALL
+ purpose_data:
+ network: foobar
+ short_name: net_environment
+ ? google_tags_tag_key_iam_binding.default["foobar:roles/resourcemanager.tagAdmin"]
+ : condition: []
+ members:
+ - user:user1@example.com
+ - user:user2@example.com
+ role: roles/resourcemanager.tagAdmin
+ google_tags_tag_value.default["foobar/one"]:
+ description: Managed by the Terraform organization module.
+ short_name: one
+ google_tags_tag_value.default["foobar/three"]:
+ description: Foobar 3.
+ short_name: three
+ google_tags_tag_value.default["foobar/two"]:
+ description: Foobar 2.
+ short_name: two
+ ? google_tags_tag_value_iam_binding.default["foobar/three:roles/resourcemanager.tagAdmin"]
+ : condition: []
+ members:
+ - user:user4@example.com
+ role: roles/resourcemanager.tagAdmin
+ ? google_tags_tag_value_iam_binding.default["foobar/three:roles/resourcemanager.tagViewer"]
+ : condition: []
+ members:
+ - user:user3@example.com
+ role: roles/resourcemanager.tagViewer
+ ? google_tags_tag_value_iam_binding.default["foobar/two:roles/resourcemanager.tagViewer"]
+ : condition: []
+ members:
+ - user:user3@example.com
+ role: roles/resourcemanager.tagViewer
+
+counts:
+ google_tags_tag_key: 4
+ google_tags_tag_key_iam_binding: 1
+ google_tags_tag_value: 5
+ google_tags_tag_value_iam_binding: 4
diff --git a/tests/modules/organization/tftest.yaml b/tests/modules/organization/tftest.yaml
index 7466614ef3..c49a139a6f 100644
--- a/tests/modules/organization/tftest.yaml
+++ b/tests/modules/organization/tftest.yaml
@@ -23,3 +23,4 @@ tests:
org_policies_boolean:
org_policies_custom_constraints:
firewall_policies_factory_combined:
+ tags:
diff --git a/tools/check_links.py b/tools/check_links.py
index 77dc617392..1e2759dfb7 100755
--- a/tools/check_links.py
+++ b/tools/check_links.py
@@ -86,7 +86,7 @@ def main(dirs, external):
state = '✓' if all(l.valid for l in doc.links) else '✗'
print(f'[{state}] {doc.relpath} ({len(doc.links)})')
if state == '✗':
- error = [f'{dir_name}{doc.relpath}']
+ error = [f'{dir_name}/{doc.relpath}']
for l in doc.links:
if not l.valid:
error.append(f' - {l.dest}')
diff --git a/tools/plan_summary.py b/tools/plan_summary.py
index 78c5f939f6..ae52c86cf2 100755
--- a/tools/plan_summary.py
+++ b/tools/plan_summary.py
@@ -15,16 +15,19 @@
# limitations under the License.
import click
+import os
import sys
import tempfile
import yaml
from pathlib import Path
-BASEDIR = Path(__file__).parents[1]
-sys.path.append(str(BASEDIR / 'tests'))
-
-import fixtures
+try:
+ import fixtures
+except ImportError:
+ BASEDIR = Path(__file__).parents[1]
+ sys.path.append(str(BASEDIR / 'tests'))
+ import fixtures
@click.command()