diff --git a/4-projects/business_unit_3/shared/README.md b/4-projects/business_unit_3/shared/README.md index cfcdd22c..b65cb8a9 100644 --- a/4-projects/business_unit_3/shared/README.md +++ b/4-projects/business_unit_3/shared/README.md @@ -11,6 +11,7 @@ | keyring\_name | Name to be used for KMS Keyring | `string` | `"sample-keyring"` | no | | location\_gcs | Case-Sensitive Location for GCS Bucket | `string` | `"US"` | no | | location\_kms | Case-Sensitive Location for KMS Keyring | `string` | `"us"` | no | +| prevent\_destroy | Prevent Project Key destruction. | `bool` | `true` | no | | project\_budget | Budget configuration.
budget\_amount: The amount to use as the budget.
alert\_spent\_percents: A list of percentages of the budget to alert on when threshold is exceeded.
alert\_pubsub\_topic: The name of the Cloud Pub/Sub topic where budget related messages will be published, in the form of `projects/{project_id}/topics/{topic_id}`.
alert\_spend\_basis: The type of basis used to determine if spend has passed the threshold. Possible choices are `CURRENT_SPEND` or `FORECASTED_SPEND` (default). |
object({
budget_amount = optional(number, 1000)
alert_spent_percents = optional(list(number), [1.2])
alert_pubsub_topic = optional(string, null)
alert_spend_basis = optional(string, "FORECASTED_SPEND")
})
| `{}` | no | | remote\_state\_bucket | Backend bucket to load Terraform Remote State Data from previous steps. | `string` | n/a | yes | | tfc\_org\_name | Name of the TFC organization | `string` | `""` | no | diff --git a/4-projects/business_unit_3/shared/ml_infra_projects.tf b/4-projects/business_unit_3/shared/ml_infra_projects.tf new file mode 100644 index 00000000..acc9abea --- /dev/null +++ b/4-projects/business_unit_3/shared/ml_infra_projects.tf @@ -0,0 +1,36 @@ +/** + * Copyright 2024 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. + */ + +module "ml_infra_project" { + source = "../../modules/ml_infra_projects" + + org_id = local.org_id + folder_id = local.common_folder_name + billing_account = local.billing_account + environment = "common" + key_rings = local.shared_kms_key_ring + business_code = "bu3" + billing_code = "1234" + primary_contact = "example@example.com" + secondary_contact = "example2@example.com" + cloud_source_artifacts_repo_name = var.cloud_source_artifacts_repo_name + cloud_source_service_catalog_repo_name = var.cloud_source_service_catalog_repo_name + remote_state_bucket = var.remote_state_bucket + artifacts_infra_pipeline_sa = module.infra_pipelines[0].terraform_service_accounts["bu3-artifact-publish"] + service_catalog_infra_pipeline_sa = module.infra_pipelines[0].terraform_service_accounts["bu3-service-catalog"] + environment_kms_project_id = "" + prevent_destroy = var.prevent_destroy +} diff --git a/4-projects/business_unit_3/shared/outputs.tf b/4-projects/business_unit_3/shared/outputs.tf index f4dfe92d..2d0e696d 100644 --- a/4-projects/business_unit_3/shared/outputs.tf +++ b/4-projects/business_unit_3/shared/outputs.tf @@ -65,30 +65,30 @@ output "enable_cloudbuild_deploy" { output "service_catalog_project_id" { description = "Service Catalog Project ID." - value = try(module.app_service_catalog_project[0].project_id, "") + value = module.ml_infra_project.service_catalog_project_id } output "common_artifacts_project_id" { description = "App Infra Artifacts Project ID" - value = try(module.app_infra_artifacts_project[0].project_id, "") + value = module.ml_infra_project.common_artifacts_project_id } output "service_catalog_repo_name" { description = "The name of the Service Catalog repository" - value = google_sourcerepo_repository.service_catalog.name + value = module.ml_infra_project.service_catalog_repo_name } output "service_catalog_repo_id" { description = "ID of the Service Catalog repository" - value = google_sourcerepo_repository.service_catalog.id + value = module.ml_infra_project.service_catalog_repo_id } output "artifacts_repo_name" { description = "The name of the Artifacts repository" - value = google_sourcerepo_repository.artifact_repo.name + value = module.ml_infra_project.artifacts_repo_name } output "artifacts_repo_id" { description = "ID of the Artifacts repository" - value = google_sourcerepo_repository.artifact_repo.id + value = module.ml_infra_project.artifacts_repo_id } diff --git a/4-projects/business_unit_3/shared/variables.tf b/4-projects/business_unit_3/shared/variables.tf index d06456fd..7efc60d6 100644 --- a/4-projects/business_unit_3/shared/variables.tf +++ b/4-projects/business_unit_3/shared/variables.tf @@ -87,3 +87,9 @@ variable "cloud_source_artifacts_repo_name" { description = "Name to give the could source repository for Artifacts" type = string } + +variable "prevent_destroy" { + description = "Prevent Project Key destruction." + type = bool + default = true +} diff --git a/4-projects/business_unit_3/shared/example_artifacts.tf b/4-projects/modules/ml_infra_projects/artifacts_project.tf similarity index 57% rename from 4-projects/business_unit_3/shared/example_artifacts.tf rename to 4-projects/modules/ml_infra_projects/artifacts_project.tf index a24433bc..e3260633 100644 --- a/4-projects/business_unit_3/shared/example_artifacts.tf +++ b/4-projects/modules/ml_infra_projects/artifacts_project.tf @@ -1,5 +1,5 @@ /** - * Copyright 2021 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,16 +27,15 @@ locals { } module "app_infra_artifacts_project" { - source = "../../modules/single_project" - # count = local.enable_cloudbuild_deploy ? 1 : 0 + source = "../ml_single_project" - org_id = local.org_id - billing_account = local.billing_account - folder_id = local.common_folder_name - environment = "common" + org_id = var.org_id + billing_account = var.billing_account + folder_id = var.folder_id + environment = var.environment project_budget = var.project_budget - project_prefix = local.project_prefix - key_rings = local.shared_kms_key_ring + project_prefix = var.project_prefix + key_rings = var.key_rings remote_state_bucket = var.remote_state_bucket activate_apis = [ "artifactregistry.googleapis.com", @@ -49,48 +48,40 @@ module "app_infra_artifacts_project" { "sourcerepo.googleapis.com", ] # Metadata - project_suffix = "artifacts" - application_name = "app-infra-artifacts" - billing_code = "1234" - primary_contact = "example@example.com" - secondary_contact = "example2@example.com" - business_code = "bu3" + project_suffix = var.artifacts_project_suffix + application_name = var.artifacts_application_name + billing_code = var.billing_code + primary_contact = var.primary_contact + secondary_contact = var.secondary_contact + business_code = var.business_code + environment_kms_project_id = var.environment_kms_project_id + project_name = "${var.project_prefix}-${local.env_code}-${var.business_code}${var.artifacts_project_suffix}" + prevent_destroy = var.prevent_destroy } -# resource "google_kms_crypto_key" "ml_key" { -# for_each = toset(local.shared_kms_key_ring) -# name = module.app_infra_artifacts_project[0].project_name -# key_ring = each.key -# rotation_period = var.key_rotation_period -# lifecycle { -# prevent_destroy = false -# } -# } - resource "google_kms_crypto_key_iam_member" "ml_key" { - for_each = module.app_infra_cloudbuild_project[0].kms_keys + for_each = module.app_infra_artifacts_project.kms_keys crypto_key_id = each.value.id role = "roles/cloudkms.admin" - member = "serviceAccount:${module.infra_pipelines[0].terraform_service_accounts["bu3-artifact-publish"]}" + member = "serviceAccount:${var.artifacts_infra_pipeline_sa}" } resource "google_project_iam_member" "artifact_tf_sa_roles" { for_each = toset(local.artifact_tf_sa_roles) - project = module.app_infra_artifacts_project[0].project_id + project = module.app_infra_artifacts_project.project_id role = each.key - member = "serviceAccount:${module.infra_pipelines[0].terraform_service_accounts["bu3-artifact-publish"]}" + member = "serviceAccount:${var.artifacts_infra_pipeline_sa}" } // Add Service Agent for Cloud Build resource "google_project_iam_member" "artifact_cloudbuild_agent" { - project = module.app_infra_artifacts_project[0].project_id + project = module.app_infra_artifacts_project.project_id role = "roles/secretmanager.secretAccessor" - member = "serviceAccount:${module.app_infra_artifacts_project[0].project_number}@cloudbuild.gserviceaccount.com" + member = "serviceAccount:${module.app_infra_artifacts_project.project_number}@cloudbuild.gserviceaccount.com" } // Add Repository for Artifact repo - resource "google_sourcerepo_repository" "artifact_repo" { - project = module.app_infra_artifacts_project[0].project_id + project = module.app_infra_artifacts_project.project_id name = var.cloud_source_artifacts_repo_name } diff --git a/4-projects/modules/ml_infra_projects/locals.tf b/4-projects/modules/ml_infra_projects/locals.tf new file mode 100644 index 00000000..32210121 --- /dev/null +++ b/4-projects/modules/ml_infra_projects/locals.tf @@ -0,0 +1,19 @@ +/** + * Copyright 2024 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 { + env_code = element(split("", var.environment), 0) +} diff --git a/4-projects/modules/ml_infra_projects/outputs.tf b/4-projects/modules/ml_infra_projects/outputs.tf new file mode 100644 index 00000000..3b73df7c --- /dev/null +++ b/4-projects/modules/ml_infra_projects/outputs.tf @@ -0,0 +1,45 @@ +/** + * Copyright 2024 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 "service_catalog_project_id" { + description = "Service Catalog Project ID." + value = try(module.app_service_catalog_project.project_id, "") +} + +output "common_artifacts_project_id" { + description = "App Infra Artifacts Project ID." + value = try(module.app_infra_artifacts_project.project_id, "") +} + +output "service_catalog_repo_name" { + description = "The name of the Service Catalog repository." + value = google_sourcerepo_repository.service_catalog.name +} + +output "service_catalog_repo_id" { + description = "ID of the Service Catalog repository." + value = google_sourcerepo_repository.service_catalog.id +} + +output "artifacts_repo_name" { + description = "The name of the Artifacts repository." + value = google_sourcerepo_repository.artifact_repo.name +} + +output "artifacts_repo_id" { + description = "ID of the Artifacts repository." + value = google_sourcerepo_repository.artifact_repo.id +} diff --git a/4-projects/business_unit_3/shared/example_service_catalog.tf b/4-projects/modules/ml_infra_projects/service_catalog_project.tf similarity index 59% rename from 4-projects/business_unit_3/shared/example_service_catalog.tf rename to 4-projects/modules/ml_infra_projects/service_catalog_project.tf index 46cae7ab..7e0a19b3 100644 --- a/4-projects/business_unit_3/shared/example_service_catalog.tf +++ b/4-projects/modules/ml_infra_projects/service_catalog_project.tf @@ -1,5 +1,5 @@ /** - * Copyright 2021 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,16 +26,15 @@ locals { } module "app_service_catalog_project" { - source = "../../modules/single_project" - # count = local.enable_cloudbuild_deploy ? 1 : 0 + source = "../ml_single_project" - org_id = local.org_id - billing_account = local.billing_account - folder_id = local.common_folder_name - environment = "common" + org_id = var.org_id + billing_account = var.billing_account + folder_id = var.folder_id + environment = var.environment project_budget = var.project_budget - project_prefix = local.project_prefix - key_rings = local.shared_kms_key_ring + project_prefix = var.project_prefix + key_rings = var.key_rings remote_state_bucket = var.remote_state_bucket activate_apis = [ "logging.googleapis.com", @@ -47,31 +46,34 @@ module "app_service_catalog_project" { "sourcerepo.googleapis.com", ] # Metadata - project_suffix = var.cloud_source_service_catalog_repo_name - application_name = "app-infra-ml" - billing_code = "1234" - primary_contact = "example@example.com" - secondary_contact = "example2@example.com" - business_code = "bu3" + project_suffix = var.service_catalog_project_suffix + application_name = var.service_catalog_application_name + billing_code = var.billing_code + primary_contact = var.primary_contact + secondary_contact = var.secondary_contact + business_code = var.business_code + environment_kms_project_id = var.environment_kms_project_id + project_name = "${var.project_prefix}-${local.env_code}-${var.business_code}${var.service_catalog_project_suffix}" + prevent_destroy = var.prevent_destroy } resource "google_kms_crypto_key_iam_member" "sc_key" { - for_each = module.app_service_catalog_project[0].kms_keys + for_each = module.app_service_catalog_project.kms_keys crypto_key_id = each.value.id role = "roles/cloudkms.admin" - member = "serviceAccount:${module.infra_pipelines[0].terraform_service_accounts["bu3-service-catalog"]}" + member = "serviceAccount:${var.service_catalog_infra_pipeline_sa}" } // Grab Service Agent for Secret Manager resource "google_project_service_identity" "secretmanager_agent" { provider = google-beta - project = module.app_service_catalog_project[0].project_id + project = module.app_service_catalog_project.project_id service = "secretmanager.googleapis.com" } // Add Secret Manager Service Agent to key with encrypt/decrypt permissions resource "google_kms_crypto_key_iam_member" "secretmanager_agent" { - for_each = module.app_service_catalog_project[0].kms_keys + for_each = module.app_service_catalog_project.kms_keys crypto_key_id = each.value.id role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" member = "serviceAccount:${google_project_service_identity.secretmanager_agent.email}" @@ -80,55 +82,43 @@ resource "google_kms_crypto_key_iam_member" "secretmanager_agent" { // Grab Service Agent for Storage resource "google_project_service_identity" "storage" { provider = google-beta - project = module.app_service_catalog_project[0].project_id + project = module.app_service_catalog_project.project_id service = "storage.googleapis.com" } // Add Service Agent for Storage resource "google_kms_crypto_key_iam_member" "storage_agent" { - for_each = module.app_service_catalog_project[0].kms_keys + for_each = module.app_service_catalog_project.kms_keys crypto_key_id = each.value.id role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" - member = "serviceAccount:service-${module.app_service_catalog_project[0].project_number}@gs-project-accounts.iam.gserviceaccount.com" + member = "serviceAccount:service-${module.app_service_catalog_project.project_number}@gs-project-accounts.iam.gserviceaccount.com" depends_on = [google_project_service_identity.storage] } // Add infra pipeline SA encrypt/decrypt permissions resource "google_kms_crypto_key_iam_member" "storage-kms-key-binding" { - for_each = module.app_service_catalog_project[0].kms_keys + for_each = module.app_service_catalog_project.kms_keys crypto_key_id = each.value.id role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" - member = "serviceAccount:${module.infra_pipelines[0].terraform_service_accounts["bu3-service-catalog"]}" + member = "serviceAccount:${var.service_catalog_infra_pipeline_sa}" } resource "google_project_iam_member" "service_catalog_tf_sa_roles" { for_each = toset(local.service_catalog_tf_sa_roles) - project = module.app_service_catalog_project[0].project_id + project = module.app_service_catalog_project.project_id role = each.key - member = "serviceAccount:${module.infra_pipelines[0].terraform_service_accounts["bu3-service-catalog"]}" + member = "serviceAccount:${var.service_catalog_infra_pipeline_sa}" } // Add Service Agent for Cloud Build resource "google_project_iam_member" "cloudbuild_agent" { - project = module.app_service_catalog_project[0].project_id + project = module.app_service_catalog_project.project_id role = "roles/secretmanager.secretAccessor" - member = "serviceAccount:${module.app_service_catalog_project[0].project_number}@cloudbuild.gserviceaccount.com" + member = "serviceAccount:${module.app_service_catalog_project.project_number}@cloudbuild.gserviceaccount.com" } // Add Service Catalog Source Repository - resource "google_sourcerepo_repository" "service_catalog" { - project = module.app_service_catalog_project[0].project_id + project = module.app_service_catalog_project.project_id name = var.cloud_source_service_catalog_repo_name } - -/** - * When Jenkins CICD is used for deployment this resource - * is created to terraform validation works. - * Without this resource, this module creates zero resources - * and it breaks terraform validation throwing the error below: - * ERROR: [Terraform plan json does not contain resource_changes key] - */ -resource "null_resource" "jenkins_cicd_service_catalog" { - count = !local.enable_cloudbuild_deploy ? 1 : 0 -} diff --git a/4-projects/modules/ml_infra_projects/variables.tf b/4-projects/modules/ml_infra_projects/variables.tf new file mode 100644 index 00000000..e9f891a9 --- /dev/null +++ b/4-projects/modules/ml_infra_projects/variables.tf @@ -0,0 +1,203 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "org_id" { + description = "The Organization ID." + type = string +} + +variable "billing_account" { + description = "The ID of the billing account to associated this project with." + type = string +} + +variable "folder_id" { + description = "The folder id where project will be created." + type = string +} + +variable "environment" { + description = "The environment the single project belongs to." + type = string +} + +variable "project_budget" { + description = < v } : { for k, v in google_kms_crypto_key.ephemeral_kms_keys : split("/", k)[3] => v } +} + +resource "google_kms_crypto_key" "ephemeral_kms_keys" { + for_each = toset(local.ephemeral_keys_for_each) + + name = var.project_name + key_ring = each.key + rotation_period = var.key_rotation_period + lifecycle { + prevent_destroy = false + } +} + +resource "google_kms_crypto_key" "kms_keys" { + for_each = toset(local.keys_for_each) + + name = var.project_name + key_ring = each.key + rotation_period = var.key_rotation_period + lifecycle { + prevent_destroy = true + } +} diff --git a/4-projects/modules/ml_kms_key/outputs.tf b/4-projects/modules/ml_kms_key/outputs.tf new file mode 100644 index 00000000..b456923a --- /dev/null +++ b/4-projects/modules/ml_kms_key/outputs.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2024 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 "kms_keys" { + description = "Keys created for the project." + value = local.output_keys +} diff --git a/4-projects/modules/ml_kms_key/variables.tf b/4-projects/modules/ml_kms_key/variables.tf new file mode 100644 index 00000000..c17ee08e --- /dev/null +++ b/4-projects/modules/ml_kms_key/variables.tf @@ -0,0 +1,36 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "key_rings" { + description = "Keyrings to attach project key to." + type = list(string) +} + +variable "project_name" { + description = "Project Name." + type = string +} + +variable "key_rotation_period" { + description = "Rotation period in seconds to be used for KMS Key." + type = string + default = "7776000s" +} + +variable "prevent_destroy" { + description = "Prevent Key destruction." + type = bool +} diff --git a/4-projects/modules/ml_kms_key/versions.tf b/4-projects/modules/ml_kms_key/versions.tf new file mode 100644 index 00000000..2ff08fd4 --- /dev/null +++ b/4-projects/modules/ml_kms_key/versions.tf @@ -0,0 +1,19 @@ +/** + * Copyright 2024 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 { + required_version = ">= 1.3" +} diff --git a/4-projects/modules/ml_single_project/README.md b/4-projects/modules/ml_single_project/README.md new file mode 100644 index 00000000..8165effa --- /dev/null +++ b/4-projects/modules/ml_single_project/README.md @@ -0,0 +1,52 @@ +# Machine Learning Single Project + +Create and manage a Google Cloud project with various configurations and roles required for application infrastructure and pipeline service accounts. It includes the setup of IAM roles, VPC networking, KMS keys, and budget alerts. The module leverages the terraform-google-modules/project-factory/google module for project creation and management. + + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| activate\_apis | The api to activate for the GCP project. | `list(string)` | `[]` | no | +| app\_infra\_pipeline\_service\_accounts | The Service Accounts from App Infra Pipeline. | `map(string)` | `{}` | no | +| application\_name | The name of application where GCP resources relate. | `string` | n/a | yes | +| billing\_account | The ID of the billing account to associated this project with. | `string` | n/a | yes | +| billing\_code | The code that's used to provide chargeback information. | `string` | n/a | yes | +| business\_code | The code that describes which business unit owns the project. | `string` | `"abcd"` | no | +| default\_service\_account | Project default service account setting: can be one of `delete`, `depriviledge`, `keep` or `disable`. | `string` | `"disable"` | no | +| enable\_cloudbuild\_deploy | Enable infra deployment using Cloud Build. | `bool` | `false` | no | +| environment | The environment the project belongs to. | `string` | n/a | yes | +| environment\_kms\_project\_id | Environment level KMS Project ID. | `string` | n/a | yes | +| folder\_id | The folder id where project will be created. | `string` | n/a | yes | +| key\_rings | Keyrings to attach project key to. | `list(string)` | n/a | yes | +| key\_rotation\_period | Rotation period in seconds to be used for KMS Key. | `string` | `"7776000s"` | no | +| org\_id | The Organization ID. | `string` | n/a | yes | +| prevent\_destroy | Prevent Key destruction. | `bool` | n/a | yes | +| primary\_contact | The primary email contact for the project. | `string` | n/a | yes | +| project\_budget | Budget configuration.
budget\_amount: The amount to use as the budget.
alert\_spent\_percents: A list of percentages of the budget to alert on when threshold is exceeded.
alert\_pubsub\_topic: The name of the Cloud Pub/Sub topic where budget related messages will be published, in the form of `projects/{project_id}/topics/{topic_id}`.
alert\_spend\_basis: The type of basis used to determine if spend has passed the threshold. Possible choices are `CURRENT_SPEND` or `FORECASTED_SPEND` (default). |
object({
budget_amount = optional(number, 1000)
alert_spent_percents = optional(list(number), [1.2])
alert_pubsub_topic = optional(string, null)
alert_spend_basis = optional(string, "FORECASTED_SPEND")
})
| `{}` | no | +| project\_name | Project Name. | `string` | n/a | yes | +| project\_prefix | Name prefix to use for projects created. | `string` | `"prj"` | no | +| project\_suffix | The name of the GCP project. Max 16 characters with 3 character business unit code. | `string` | n/a | yes | +| remote\_state\_bucket | Backend bucket to load Terraform Remote State Data from previous steps. | `string` | n/a | yes | +| sa\_roles | A list of roles to give the Service Account from App Infra Pipeline. | `map(list(string))` | `{}` | no | +| secondary\_contact | The secondary email contact for the project. | `string` | `""` | no | +| shared\_vpc\_host\_project\_id | Shared VPC host project ID. | `string` | `""` | no | +| shared\_vpc\_subnets | List of the shared vpc subnets self links. | `list(string)` | `[]` | no | +| vpc\_service\_control\_attach\_enabled | Whether the project will be attached to a VPC Service Control Perimeter. | `bool` | `false` | no | +| vpc\_service\_control\_perimeter\_name | The name of a VPC Service Control Perimeter to add the created project to. | `string` | `null` | no | +| vpc\_service\_control\_sleep\_duration | The duration to sleep in seconds before adding the project to a shared VPC after the project is added to the VPC Service Control Perimeter. | `string` | `"5s"` | no | +| vpc\_type | The type of VPC to attach the project to. Possible options are `base` or `restricted`. | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| enabled\_apis | VPC Service Control services. | +| kms\_keys | Keys created for the project. | +| project\_id | Project ID. | +| project\_name | Project Name. | +| project\_number | Project number. | +| sa | Project SA email. | + + diff --git a/4-projects/modules/ml_single_project/main.tf b/4-projects/modules/ml_single_project/main.tf new file mode 100644 index 00000000..50afa4b1 --- /dev/null +++ b/4-projects/modules/ml_single_project/main.tf @@ -0,0 +1,141 @@ +/** + * Copyright 2024 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 { + enabled_apis = distinct(concat(var.activate_apis, ["billingbudgets.googleapis.com"])) + env_code = element(split("", var.environment), 0) + source_repos = setintersection( + toset(keys(var.app_infra_pipeline_service_accounts)), + toset(keys(var.sa_roles)) + ) + pipeline_roles = var.enable_cloudbuild_deploy ? flatten([ + for repo in local.source_repos : [ + for role in var.sa_roles[repo] : + { + repo = repo + role = role + sa = var.app_infra_pipeline_service_accounts[repo] + } + ] + ]) : [] + + pipeline_kms_sas = var.enable_cloudbuild_deploy ? flatten([ + for repo in keys(var.sa_roles) : [ + var.app_infra_pipeline_service_accounts[repo] + ] + ]) : [] + + network_user_role = var.enable_cloudbuild_deploy ? flatten([ + for repo in local.source_repos : [ + for subnet in var.shared_vpc_subnets : + { + repo = repo + subnet = element(split("/", subnet), index(split("/", subnet), "subnetworks", ) + 1, ) + region = element(split("/", subnet), index(split("/", subnet), "regions") + 1, ) + sa = var.app_infra_pipeline_service_accounts[repo] + } + ] + ]) : [] +} + +module "project" { + source = "terraform-google-modules/project-factory/google" + version = "~> 14.1" + + random_project_id = true + random_project_id_length = 4 + activate_apis = local.enabled_apis + name = var.project_name + org_id = var.org_id + billing_account = var.billing_account + folder_id = var.folder_id + default_service_account = var.default_service_account + + svpc_host_project_id = var.shared_vpc_host_project_id + shared_vpc_subnets = var.shared_vpc_subnets # Optional: To enable subnetting, replace to "module.networking_project.subnetwork_self_link" + + vpc_service_control_attach_enabled = var.vpc_service_control_attach_enabled + vpc_service_control_perimeter_name = var.vpc_service_control_perimeter_name + vpc_service_control_sleep_duration = var.vpc_service_control_sleep_duration + + labels = { + environment = var.environment + application_name = var.application_name + billing_code = var.billing_code + primary_contact = element(split("@", var.primary_contact), 0) + secondary_contact = element(split("@", var.secondary_contact), 0) + business_code = var.business_code + env_code = local.env_code + vpc_type = var.vpc_type + } + budget_alert_pubsub_topic = var.project_budget.alert_pubsub_topic + budget_alert_spent_percents = var.project_budget.alert_spent_percents + budget_amount = var.project_budget.budget_amount + budget_alert_spend_basis = var.project_budget.alert_spend_basis +} + +# Additional roles to the App Infra Pipeline service account +resource "google_project_iam_member" "app_infra_pipeline_sa_roles" { + for_each = { for pr in local.pipeline_roles : "${pr.repo}-${pr.sa}-${pr.role}" => pr } + + project = module.project.project_id + role = each.value.role + member = "serviceAccount:${each.value.sa}" +} + +resource "google_folder_iam_member" "folder_network_viewer" { + for_each = var.app_infra_pipeline_service_accounts + + folder = var.folder_id + role = "roles/compute.networkViewer" + member = "serviceAccount:${each.value}" +} + +resource "google_project_iam_member" "shared_vpc_network_viewer" { + for_each = var.shared_vpc_host_project_id != "" ? toset(local.pipeline_kms_sas) : toset([]) + + project = var.shared_vpc_host_project_id + role = "roles/compute.networkViewer" + member = "serviceAccount:${each.key}" +} + +resource "google_compute_subnetwork_iam_member" "account_role_to_vpc_subnets" { + provider = google-beta + for_each = { for nr in local.network_user_role : "${nr.repo}-${nr.subnet}-${nr.sa}" => nr } + + subnetwork = each.value.subnet + role = "roles/compute.networkUser" + region = each.value.region + project = var.shared_vpc_host_project_id + member = "serviceAccount:${each.value.sa}" +} + +// Add key for project +module "kms_keys" { + source = "../ml_kms_key" + key_rings = var.key_rings + key_rotation_period = var.key_rotation_period + project_name = module.project.project_name + prevent_destroy = var.prevent_destroy +} + +// Add crypto key viewer role to kms environment project +resource "google_project_iam_member" "kms_viewer" { + for_each = var.environment != "common" ? toset(local.pipeline_kms_sas) : toset([]) + project = var.environment_kms_project_id + role = "roles/cloudkms.viewer" + member = "serviceAccount:${each.key}" +} diff --git a/4-projects/modules/ml_single_project/outputs.tf b/4-projects/modules/ml_single_project/outputs.tf new file mode 100644 index 00000000..b4aba602 --- /dev/null +++ b/4-projects/modules/ml_single_project/outputs.tf @@ -0,0 +1,45 @@ +/** + * Copyright 2024 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 "project_id" { + description = "Project ID." + value = module.project.project_id +} + +output "sa" { + description = "Project SA email." + value = module.project.service_account_email +} + +output "project_number" { + description = "Project number." + value = module.project.project_number +} + +output "enabled_apis" { + description = "VPC Service Control services." + value = local.enabled_apis +} + +output "project_name" { + description = "Project Name." + value = module.project.project_name +} + +output "kms_keys" { + description = "Keys created for the project." + value = module.kms_keys.kms_keys +} diff --git a/4-projects/modules/ml_single_project/variables.tf b/4-projects/modules/ml_single_project/variables.tf new file mode 100644 index 00000000..d55b3647 --- /dev/null +++ b/4-projects/modules/ml_single_project/variables.tf @@ -0,0 +1,187 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "org_id" { + description = "The Organization ID." + type = string +} + +variable "folder_id" { + description = "The folder id where project will be created." + type = string +} + +variable "billing_account" { + description = "The ID of the billing account to associated this project with." + type = string +} + +variable "project_suffix" { + description = "The name of the GCP project. Max 16 characters with 3 character business unit code." + type = string +} + +variable "application_name" { + description = "The name of application where GCP resources relate." + type = string +} + +variable "billing_code" { + description = "The code that's used to provide chargeback information." + type = string +} + +variable "primary_contact" { + description = "The primary email contact for the project." + type = string +} + +variable "secondary_contact" { + description = "The secondary email contact for the project." + type = string + default = "" +} + +variable "business_code" { + description = "The code that describes which business unit owns the project." + type = string + default = "abcd" +} + +variable "activate_apis" { + description = "The api to activate for the GCP project." + type = list(string) + default = [] +} + +variable "environment" { + description = "The environment the project belongs to." + type = string +} + +variable "vpc_type" { + description = "The type of VPC to attach the project to. Possible options are `base` or `restricted`." + type = string + default = "" +} + +variable "shared_vpc_host_project_id" { + description = "Shared VPC host project ID." + type = string + default = "" +} + +variable "shared_vpc_subnets" { + description = "List of the shared vpc subnets self links." + type = list(string) + default = [] +} + +variable "vpc_service_control_attach_enabled" { + description = "Whether the project will be attached to a VPC Service Control Perimeter." + type = bool + default = false +} + +variable "vpc_service_control_perimeter_name" { + description = "The name of a VPC Service Control Perimeter to add the created project to." + type = string + default = null +} + +variable "vpc_service_control_sleep_duration" { + description = "The duration to sleep in seconds before adding the project to a shared VPC after the project is added to the VPC Service Control Perimeter." + type = string + default = "5s" +} + +variable "project_budget" { + description = <