diff --git a/fast/stages/3-project-factory/prod/README.md b/fast/stages/3-project-factory/prod/README.md new file mode 100644 index 00000000..b9eb9c76 --- /dev/null +++ b/fast/stages/3-project-factory/prod/README.md @@ -0,0 +1,129 @@ +# Project factory + +The Project Factory (or PF) 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](https://medium.com/google-cloud/resource-factories-a-descriptive-approach-to-terraform-581b3ebb59c). + +## Design overview and choices + +
+
+
project-factory
|
+| [outputs.tf](./outputs.tf) | Module outputs. | |
+| [variables.tf](./variables.tf) | Module variables. | |
+
+## Variables
+
+| name | description | type | required | default | producer |
+|---|---|:---:|:---:|:---:|:---:|
+| [billing_account](variables.tf#L19) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…})
| ✓ | | 0-bootstrap
|
+| [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
+
+| name | description | sensitive | consumers |
+|---|---|:---:|---|
+| [projects](outputs.tf#L17) | Created projects and service accounts. | | |
+
+
diff --git a/fast/stages/3-project-factory/prod/data/defaults.yaml b/fast/stages/3-project-factory/prod/data/defaults.yaml
new file mode 100644
index 00000000..994ca6b2
--- /dev/null
+++ b/fast/stages/3-project-factory/prod/data/defaults.yaml
@@ -0,0 +1,23 @@
+# skip boilerplate check
+
+billing_account_id: 01EBC4-8CD936-3108EA
+
+# [opt] Setup for billing alerts
+billing_alert:
+ amount: 1000
+ thresholds:
+ current: [0.5, 0.8]
+ forecasted: [1.2, 1.5]
+ credit_treatment: INCLUDE_ALL_CREDITS
+
+# [opt] Contacts for billing alerts and important notifications
+essential_contacts: ["admin@zfnd.org"]
+
+# [opt] Labels set for all projects
+labels:
+ environment: prod
+ department: engineering
+
+
+# [opt] Additional notification channels for billing
+notification_channels: []
diff --git a/fast/stages/3-project-factory/prod/data/projects/prod-services.yaml b/fast/stages/3-project-factory/prod/data/projects/prod-services.yaml
new file mode 100644
index 00000000..af0c03ce
--- /dev/null
+++ b/fast/stages/3-project-factory/prod/data/projects/prod-services.yaml
@@ -0,0 +1,106 @@
+# skip boilerplate check
+
+# [opt] Billing alerts config - overrides default if set
+billing_alert:
+ amount: 5000
+ thresholds:
+ current:
+ - 0.8
+ - 1.0
+ forecasted: []
+ credit_treatment: INCLUDE_ALL_CREDITS
+
+# [opt] DNS zones to be created as children of the environment_dns_zone defined in defaults
+dns_zones: []
+
+# [opt] Contacts for billing alerts and important notifications
+essential_contacts:
+ - devops@zfnd.org
+
+# Folder the project will be created as children of
+folder_id: folders/516886086110
+
+# [opt] Authoritative IAM bindings in group => [roles] format
+group_iam:
+ engineers@zfnd.org:
+ - roles/viewer
+
+# [opt] Authoritative IAM bindings in role => [principals] format
+# Generally used to grant roles to service accounts external to the project
+iam:
+ roles/iam.workloadIdentityUser:
+ - principalSet://iam.googleapis.com/projects/771011584009/locations/global/workloadIdentityPools/zfnd-bootstrap/*
+ # roles/editor:
+ # - serviceAccount:1059680692020@cloudservices.gserviceaccount.com
+
+# [opt] Service robots and keys they will be assigned as cryptoKeyEncrypterDecrypter
+# in service => [keys] format
+# kms_service_agents:
+# compute: [key1, key2]
+# storage: [key1, key2]
+
+# [opt] Labels for the project - merged with the ones defined in defaults
+labels:
+ environment: prod
+ application: services
+ scope: ecosystem
+
+# [opt] Org policy overrides defined at project level
+org_policies:
+ iam.allowedPolicyMemberDomains:
+ rules:
+ - allow:
+ all: true
+
+# [opt] Service account to create for the project and their roles on the project
+# in name => [roles] format
+service_accounts:
+ instance-deployer:
+ - roles/compute.instanceAdmin
+ - roles/compute.storageAdmin
+ - roles/compute.loadBalancerAdmin
+ - roles/errorreporting.user
+ - roles/logging.logWriter
+ - roles/monitoring.metricWriter
+ - roles/artifactregistry.reader
+ - roles/iam.serviceAccountUser
+ - roles/iam.workloadIdentityUser
+
+# [opt] APIs to enable on the project.
+services:
+ # - artifactregistry.googleapis.com
+ - compute.googleapis.com
+ # - clouddebugger.googleapis.com
+ - clouderrorreporting.googleapis.com
+ - cloudresourcemanager.googleapis.com
+ - containeranalysis.googleapis.com
+ - logging.googleapis.com
+ - monitoring.googleapis.com
+ - osconfig.googleapis.com
+ - networkmanagement.googleapis.com
+ - stackdriver.googleapis.com
+ - storage.googleapis.com
+ - iap.googleapis.com
+
+# [opt] Roles to assign to the service identities in service => [roles] format
+service_identities_iam:
+ compute:
+ - roles/storage.objectViewer
+
+ # [opt] VPC setup.
+ # If set enables the `compute.googleapis.com` service and configures
+ # service project attachment
+
+vpc:
+ # [opt] If set, enables the container API
+ gke_setup: null
+
+ # Host project the project will be service project of
+ host_project: zfnd-prod-net-spoke-0
+
+ # [opt] Subnets in the host project where principals will be granted networkUser
+ # in region/subnet-name => [principals]
+ subnets_iam:
+ us-east1/prod-default-ue1:
+ # - user:gustavo@zfnd.org
+ - serviceAccount:instance-deployer@zfnd-prod-zebra.iam.gserviceaccount.com
diff --git a/fast/stages/3-project-factory/prod/data/projects/prod-zebra.yaml b/fast/stages/3-project-factory/prod/data/projects/prod-zebra.yaml
new file mode 100644
index 00000000..4f0fba24
--- /dev/null
+++ b/fast/stages/3-project-factory/prod/data/projects/prod-zebra.yaml
@@ -0,0 +1,110 @@
+# skip boilerplate check
+
+# [opt] Billing alerts config - overrides default if set
+billing_alert:
+ amount: 5000
+ thresholds:
+ current:
+ - 0.8
+ - 1.0
+ forecasted: []
+ credit_treatment: INCLUDE_ALL_CREDITS
+
+# [opt] DNS zones to be created as children of the environment_dns_zone defined in defaults
+dns_zones: []
+
+# [opt] Contacts for billing alerts and important notifications
+essential_contacts:
+ - devops@zfnd.org
+
+# Folder the project will be created as children of
+folder_id: folders/516886086110
+
+# [opt] Authoritative IAM bindings in group => [roles] format
+group_iam:
+ engineers@zfnd.org:
+ - roles/viewer
+
+# [opt] Authoritative IAM bindings in role => [principals] format
+# Generally used to grant roles to service accounts external to the project
+iam:
+ roles/iam.workloadIdentityUser:
+ - principalSet://iam.googleapis.com/projects/771011584009/locations/global/workloadIdentityPools/zfnd-bootstrap/*
+ # roles/editor:
+ # - serviceAccount:1059680692020@cloudservices.gserviceaccount.com
+
+# [opt] Service robots and keys they will be assigned as cryptoKeyEncrypterDecrypter
+# in service => [keys] format
+# kms_service_agents:
+# compute: [key1, key2]
+# storage: [key1, key2]
+
+# [opt] Labels for the project - merged with the ones defined in defaults
+labels:
+ environment: prod
+ application: zebra
+ firebase: enabled
+
+# [opt] Org policy overrides defined at project level
+org_policies:
+ iam.allowedPolicyMemberDomains:
+ rules:
+ - allow:
+ all: true
+
+# [opt] Service account to create for the project and their roles on the project
+# in name => [roles] format
+service_accounts:
+ instance-deployer:
+ - roles/compute.instanceAdmin
+ - roles/compute.storageAdmin
+ - roles/compute.loadBalancerAdmin
+ - roles/errorreporting.user
+ - roles/logging.logWriter
+ - roles/monitoring.metricWriter
+ - roles/artifactregistry.reader
+ - roles/iam.serviceAccountUser
+ - roles/iam.workloadIdentityUser
+ documentation-publisher:
+ - roles/firebasehosting.admin
+
+# [opt] APIs to enable on the project.
+services:
+ # - artifactregistry.googleapis.com
+ - compute.googleapis.com
+ - clouderrorreporting.googleapis.com
+ - cloudresourcemanager.googleapis.com
+ - containeranalysis.googleapis.com
+ - firebase.googleapis.com
+ - iap.googleapis.com
+ - logging.googleapis.com
+ - monitoring.googleapis.com
+ - osconfig.googleapis.com
+ - networkmanagement.googleapis.com
+ - serviceusage.googleapis.com
+ - stackdriver.googleapis.com
+ - storage.googleapis.com
+
+
+# [opt] Roles to assign to the service identities in service => [roles] format
+service_identities_iam:
+ compute:
+ - roles/storage.objectViewer
+
+ # [opt] VPC setup.
+ # If set enables the `compute.googleapis.com` service and configures
+ # service project attachment
+
+vpc:
+ # [opt] If set, enables the container API
+ gke_setup: null
+
+ # Host project the project will be service project of
+ host_project: zfnd-prod-net-spoke-0
+
+ # [opt] Subnets in the host project where principals will be granted networkUser
+ # in region/subnet-name => [principals]
+ subnets_iam:
+ us-east1/prod-default-ue1:
+ # - user:gustavo@zfnd.org
+ - serviceAccount:instance-deployer@zfnd-prod-zebra.iam.gserviceaccount.com
diff --git a/fast/stages/3-project-factory/prod/data/projects/project.yaml.sample b/fast/stages/3-project-factory/prod/data/projects/project.yaml.sample
new file mode 100644
index 00000000..5311019d
--- /dev/null
+++ b/fast/stages/3-project-factory/prod/data/projects/project.yaml.sample
@@ -0,0 +1,103 @@
+# skip boilerplate check
+
+# [opt] Billing account id - overrides default if set
+billing_account_id: 012345-67890A-BCDEF0
+
+# [opt] Billing alerts config - overrides default if set
+billing_alert:
+ amount: 10
+ thresholds:
+ current:
+ - 0.5
+ - 0.8
+ forecasted: []
+ credit_treatment: INCLUDE_ALL_CREDITS
+
+# [opt] DNS zones to be created as children of the environment_dns_zone defined in defaults
+dns_zones:
+ - lorem
+ - ipsum
+
+# [opt] Contacts for billing alerts and important notifications
+essential_contacts:
+ - team-a-contacts@example.com
+
+# Folder the project will be created as children of
+folder_id: folders/012345678901
+
+# [opt] Authoritative IAM bindings in group => [roles] format
+group_iam:
+ test-team-foobar@fast-lab-0.gcp-pso-italy.net:
+ - roles/compute.admin
+
+# [opt] Authoritative IAM bindings in role => [principals] format
+# Generally used to grant roles to service accounts external to the project
+iam:
+ roles/compute.admin:
+ - serviceAccount:service-account
+
+# [opt] Service robots and keys they will be assigned as cryptoKeyEncrypterDecrypter
+# in service => [keys] format
+kms_service_agents:
+ compute: [key1, key2]
+ storage: [key1, key2]
+
+# [opt] Labels for the project - merged with the ones defined in defaults
+labels:
+ environment: dev
+
+# [opt] Org policy overrides defined at project level
+org_policies:
+ compute.disableGuestAttributesAccess:
+ rules:
+ - enforce: true
+ compute.trustedImageProjects:
+ rules:
+ - allow:
+ values:
+ - projects/fast-dev-iac-core-0
+ compute.vmExternalIpAccess:
+ rules:
+ - deny:
+ all: true
+
+# [opt] Service account to create for the project and their roles on the project
+# in name => [roles] format
+service_accounts:
+ another-service-account:
+ - roles/compute.admin
+ my-service-account:
+ - roles/compute.admin
+
+# [opt] APIs to enable on the project.
+services:
+ - storage.googleapis.com
+ - stackdriver.googleapis.com
+ - compute.googleapis.com
+
+# [opt] Roles to assign to the service identities in service => [roles] format
+service_identities_iam:
+ compute:
+ - roles/storage.objectViewer
+
+ # [opt] VPC setup.
+ # If set enables the `compute.googleapis.com` service and configures
+ # service project attachment
+vpc:
+ # [opt] If set, enables the container API
+ gke_setup:
+ # Grants "roles/container.hostServiceAgentUser" to the container robot if set
+ enable_host_service_agent: false
+
+ # Grants "roles/compute.securityAdmin" to the container robot if set
+ enable_security_admin: true
+
+ # Host project the project will be service project of
+ host_project: fast-dev-net-spoke-0
+
+ # [opt] Subnets in the host project where principals will be granted networkUser
+ # in region/subnet-name => [principals]
+ subnets_iam:
+ europe-west1/dev-default-ew1:
+ - user:foobar@example.com
+ - serviceAccount:service-account1
diff --git a/fast/stages/3-project-factory/prod/diagram.png b/fast/stages/3-project-factory/prod/diagram.png
new file mode 100644
index 00000000..b942ea47
Binary files /dev/null and b/fast/stages/3-project-factory/prod/diagram.png differ
diff --git a/fast/stages/3-project-factory/prod/diagram.svg b/fast/stages/3-project-factory/prod/diagram.svg
new file mode 100644
index 00000000..d7821c60
--- /dev/null
+++ b/fast/stages/3-project-factory/prod/diagram.svg
@@ -0,0 +1,1530 @@
+
+
diff --git a/fast/stages/3-project-factory/prod/main.tf b/fast/stages/3-project-factory/prod/main.tf
new file mode 100644
index 00000000..72b1a3cb
--- /dev/null
+++ b/fast/stages/3-project-factory/prod/main.tf
@@ -0,0 +1,115 @@
+/**
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Project factory.
+
+
+locals {
+ _defaults = yamldecode(file(var.defaults_file))
+ _defaults_net = {
+ billing_account_id = var.billing_account.id
+ environment_dns_zone = var.environment_dns_zone
+ shared_vpc_self_link = try(var.vpc_self_links["prod-spoke-0"], null)
+ vpc_host_project = try(var.host_project_ids["prod-spoke-0"], null)
+ }
+ defaults = merge(local._defaults, local._defaults_net)
+ projects = {
+ for f in fileset("${var.data_dir}", "**/*.yaml") :
+ trimsuffix(f, ".yaml") => yamldecode(file("${var.data_dir}/${f}"))
+ }
+}
+
+module "projects" {
+ source = "../../../../blueprints/factories/project-factory"
+ for_each = local.projects
+ defaults = local.defaults
+ project_id = each.key
+ billing_account_id = try(each.value.billing_account_id, null)
+ billing_alert = try(each.value.billing_alert, null)
+ dns_zones = try(each.value.dns_zones, [])
+ essential_contacts = try(each.value.essential_contacts, [])
+ folder_id = try(each.value.folder_id, local.defaults.folder_id)
+ group_iam = try(each.value.group_iam, {})
+ iam = try(each.value.iam, {})
+ kms_service_agents = try(each.value.kms, {})
+ labels = try(each.value.labels, {})
+ org_policies = try(each.value.org_policies, null)
+ prefix = var.prefix
+ service_accounts = try(each.value.service_accounts, {})
+ service_accounts_iam = try(each.value.service_accounts_iam, {})
+ services = try(each.value.services, [])
+ service_identities_iam = try(each.value.service_identities_iam, {})
+ vpc = try(each.value.vpc, null)
+}
+
+# Enables Firebase services for the new project created above.
+resource "google_firebase_project" "firebase-zebra-docs" {
+ provider = google-beta
+ project = "zfnd-prod-zebra"
+
+ # Waits for the required APIs to be enabled.
+ depends_on = [
+ module.projects.services
+ ]
+}
+
+resource "google_firebase_web_app" "zebra-book" {
+ provider = google-beta
+ project = "zfnd-prod-zebra"
+ display_name = "Zebra Book"
+ deletion_policy = "DELETE"
+
+ depends_on = [google_firebase_project.firebase-zebra-docs]
+}
+
+resource "google_firebase_hosting_site" "zebra-book" {
+ provider = google-beta
+ project = "zfnd-prod-zebra"
+ site_id = "zebra-docs-book"
+ app_id = google_firebase_web_app.zebra-book.app_id
+}
+
+resource "google_firebase_web_app" "zebra-docs-internal" {
+ provider = google-beta
+ project = "zfnd-prod-zebra"
+ display_name = "Zebra Docs - Internal"
+ deletion_policy = "DELETE"
+
+ depends_on = [google_firebase_project.firebase-zebra-docs]
+}
+
+resource "google_firebase_hosting_site" "zebra-docs-internal" {
+ provider = google-beta
+ project = "zfnd-prod-zebra"
+ site_id = "zebra-docs-internal"
+ app_id = google_firebase_web_app.zebra-docs-internal.app_id
+}
+
+resource "google_firebase_web_app" "zebra-docs-external" {
+ provider = google-beta
+ project = "zfnd-prod-zebra"
+ display_name = "Zebra Docs - External"
+ deletion_policy = "DELETE"
+
+ depends_on = [google_firebase_project.firebase-zebra-docs]
+}
+
+resource "google_firebase_hosting_site" "zebra-docs-external" {
+ provider = google-beta
+ project = "zfnd-prod-zebra"
+ site_id = "zebra-docs-external"
+ app_id = google_firebase_web_app.zebra-docs-external.app_id
+}
diff --git a/fast/stages/3-project-factory/prod/outputs.tf b/fast/stages/3-project-factory/prod/outputs.tf
new file mode 100644
index 00000000..59ecff95
--- /dev/null
+++ b/fast/stages/3-project-factory/prod/outputs.tf
@@ -0,0 +1,20 @@
+/**
+ * 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.
+ */
+
+output "projects" {
+ description = "Created projects and service accounts."
+ value = module.projects
+}
diff --git a/fast/stages/3-project-factory/prod/variables.tf b/fast/stages/3-project-factory/prod/variables.tf
new file mode 100644
index 00000000..934f4e40
--- /dev/null
+++ b/fast/stages/3-project-factory/prod/variables.tf
@@ -0,0 +1,142 @@
+/**
+ * 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.
+ */
+
+#TODO: tfdoc annotations
+
+variable "billing_account" {
+ # 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
+ 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" {
+ description = "Relative path for the folder storing configuration data."
+ type = string
+ default = "data/projects"
+}
+
+variable "defaults_file" {
+ description = "Relative path for the file storing the project factory configuration."
+ type = string
+ default = "data/defaults.yaml"
+}
+
+variable "environment_dns_zone" {
+ # tfdoc:variable:source 2-networking
+ description = "DNS zone suffix for environment."
+ type = string
+ default = null
+}
+
+variable "host_project_ids" {
+ # tfdoc:variable:source 2-networking
+ description = "Host project for the shared VPC."
+ type = object({
+ prod-spoke-0 = string
+ })
+ default = null
+}
+
+variable "prefix" {
+ # tfdoc:variable:source 0-bootstrap
+ description = "Prefix used for resources that need unique names. Use 9 characters or less."
+ type = string
+
+ validation {
+ condition = try(length(var.prefix), 0) < 10
+ error_message = "Use a maximum of 9 characters for prefix."
+ }
+}
+
+variable "vpc_self_links" {
+ # tfdoc:variable:source 2-networking
+ description = "Self link for the shared VPC."
+ type = object({
+ prod-spoke-0 = string
+ })
+ default = null
+}
+
+# self-hosted runners variables
+
+variable "project_id" {
+ type = string
+ description = "The project id to deploy Github Runner MIG"
+}
+
+variable "image" {
+ type = string
+ description = "The github runner image"
+}
+
+variable "repo_url" {
+ type = string
+ description = "Repo URL for the Github Action"
+}
+
+
+variable "repo_name" {
+ type = string
+ description = "Name of the repo for the Github Action"
+}
+
+
+variable "repo_owner" {
+ type = string
+ description = "Owner of the repo for the Github Action"
+}
+
+variable "gh_token" {
+ type = string
+ description = "Github token that is used for generating Self Hosted Runner Token"
+}
+
+variable "region" {
+ type = string
+ description = "The GCP region to deploy instances into"
+ default = "us-east1"
+}
+
+variable "subnetwork_project" {
+ type = string
+ description = "The ID of the project in which the subnetwork belongs. If it is not provided, the project_id is used."
+ default = ""
+}
+
+variable "subnet_name" {
+ type = string
+ description = "Name for the subnet"
+ default = ""
+}
+
+variable "subnet_ip" {
+ type = string
+ description = "IP range for the subnet"
+ default = ""
+}
+
+variable "service_account" {
+ description = "Service account email address"
+ type = string
+ default = ""
+}
\ No newline at end of file