From be3a987f12e16f5aafbf7f1259d994c3909ac3f0 Mon Sep 17 00:00:00 2001 From: Ludo Date: Wed, 10 Apr 2024 13:22:55 +0200 Subject: [PATCH 1/7] add support for quotas to project module --- fast/stages/0-bootstrap/automation.tf | 1 + modules/project/README.md | 23 ++++++++++++++ modules/project/outputs.tf | 13 ++++++++ modules/project/quotas.tf | 34 +++++++++++++++++++++ modules/project/variables-quotas.tf | 44 +++++++++++++++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 modules/project/quotas.tf create mode 100644 modules/project/variables-quotas.tf diff --git a/fast/stages/0-bootstrap/automation.tf b/fast/stages/0-bootstrap/automation.tf index b6489ef04d..6200deab38 100644 --- a/fast/stages/0-bootstrap/automation.tf +++ b/fast/stages/0-bootstrap/automation.tf @@ -134,6 +134,7 @@ module "automation-project" { "cloudasset.googleapis.com", "cloudbilling.googleapis.com", "cloudkms.googleapis.com", + "cloudquotas.googleapis.com", "cloudresourcemanager.googleapis.com", "essentialcontacts.googleapis.com", "iam.googleapis.com", diff --git a/modules/project/README.md b/modules/project/README.md index a4115e8cb6..6599b6f6e7 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -797,6 +797,29 @@ includedPermissions: - resourcemanager.projects.list ``` +## Quotas + +Project and regional quotas can be managed via the `quotas` variable, but require configuring the quota project used by the Terraform provider. This can be done at the provider level via the `user_project_override` and `billing_project` [provider configurations](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference.html#user_project_override). Note that some module functionality will break with this configuration, so we recommend [definining an alias](https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations) for the Google provider changing module so that the quota resource uses it. + +```hcl +module "project" { + source = "./fabric/modules/project" + name = "project" + quotas = { + cpus-ew8 = { + service = "compute.googleapis.com" + quota_id = "CPUS-per-project-region" + contact_email = "user@example.com" + preferred_value = 80 + dimensions = { + region = "europe-west8" + } + } + } +} +# tftest modules=1 resources=2 +``` + ## Outputs Most of this module's outputs depend on its resources, to allow Terraform to compute all dependencies required for the project to be correctly configured. This allows you to reference outputs like `project_id` in other modules or resources without having to worry about setting `depends_on` blocks manually. diff --git a/modules/project/outputs.tf b/modules/project/outputs.tf index c55aa52c31..511be50655 100644 --- a/modules/project/outputs.tf +++ b/modules/project/outputs.tf @@ -98,6 +98,19 @@ output "project_id" { ] } +output "quota_configs" { + description = "Quota configurations." + value = { + for k, v in google_cloud_quotas_quota_preference.default : + "${v.service}/${v.quota_id}" => v.quota_config.0.preferred_value + } +} + +output "quotas" { + description = "Quota resources." + value = google_cloud_quotas_quota_preference.default +} + output "service_accounts" { description = "Product robot service accounts in project." value = { diff --git a/modules/project/quotas.tf b/modules/project/quotas.tf new file mode 100644 index 0000000000..a261873db1 --- /dev/null +++ b/modules/project/quotas.tf @@ -0,0 +1,34 @@ +/** + * 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. + */ + +resource "google_cloud_quotas_quota_preference" "default" { + for_each = var.quotas + parent = "projects/${local.project.project_id}" + name = each.key + service = each.value.service + dimensions = each.value.dimensions + quota_id = each.value.quota_id + contact_email = each.value.contact_email + justification = each.value.justification + quota_config { + preferred_value = each.value.preferred_value + annotations = each.value.annotations + } + ignore_safety_checks = each.value.ignore_safety_checks + depends_on = [ + google_project_service.project_services + ] +} diff --git a/modules/project/variables-quotas.tf b/modules/project/variables-quotas.tf new file mode 100644 index 0000000000..d2a7b65ff4 --- /dev/null +++ b/modules/project/variables-quotas.tf @@ -0,0 +1,44 @@ +/** + * 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 "quotas" { + description = "Service quota configuration." + type = map(object({ + service = string + quota_id = string + preferred_value = number + dimensions = optional(map(string), {}) + justification = optional(string) + contact_email = optional(string) + annotations = optional(map(string)) + ignore_safety_checks = optional(string) + })) + nullable = false + default = {} + validation { + condition = alltrue([ + for k, v in var.quotas : + v.ignore_safety_checks == null || contains( + [ + "QUOTA_DECREASE_BELOW_USAGE", + "QUOTA_DECREASE_PERCENTAGE_TOO_HIGH", + "QUOTA_SAFETY_CHECK_UNSPECIFIED" + ], + coalesce(v.ignore_safety_checks, "-") + )]) + error_message = "Invalid value for ignore safety checks." + } +} From 1aedfe7735c92929f004d201fba2a99c22ded361 Mon Sep 17 00:00:00 2001 From: Ludo Date: Wed, 10 Apr 2024 13:23:48 +0200 Subject: [PATCH 2/7] tfdoc --- modules/project/README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/modules/project/README.md b/modules/project/README.md index 6599b6f6e7..63d683e3bd 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -22,6 +22,7 @@ This module implements the creation and management of one GCP project including - [Project-scoped Tags](#project-scoped-tags) - [Custom Roles](#custom-roles) - [Custom Roles Factory](#custom-roles-factory) +- [Quotas](#quotas) - [Outputs](#outputs) - [Managing project related configuration without creating it](#managing-project-related-configuration-without-creating-it) - [Files](#files) @@ -1067,10 +1068,12 @@ module "bucket" { | [main.tf](./main.tf) | Module-level locals and resources. | google_compute_project_metadata_item · google_essential_contacts_contact · google_monitoring_monitored_project · google_project · google_project_service · google_resource_manager_lien | | [organization-policies.tf](./organization-policies.tf) | Project-level organization policies. | google_org_policy_policy | | [outputs.tf](./outputs.tf) | Module outputs. | | +| [quotas.tf](./quotas.tf) | None | google_cloud_quotas_quota_preference | | [service-accounts.tf](./service-accounts.tf) | Service identities and supporting resources. | google_kms_crypto_key_iam_member · google_project_default_service_accounts · google_project_iam_member · google_project_service_identity | | [shared-vpc.tf](./shared-vpc.tf) | Shared VPC project-level configuration. | google_compute_shared_vpc_host_project · google_compute_shared_vpc_service_project · google_compute_subnetwork_iam_member · google_project_iam_member | | [tags.tf](./tags.tf) | None | google_tags_tag_binding · google_tags_tag_key · google_tags_tag_key_iam_binding · google_tags_tag_value · google_tags_tag_value_iam_binding | | [variables-iam.tf](./variables-iam.tf) | None | | +| [variables-quotas.tf](./variables-quotas.tf) | None | | | [variables-tags.tf](./variables-tags.tf) | None | | | [variables.tf](./variables.tf) | Module variables. | | | [versions.tf](./versions.tf) | Version pins. | | @@ -1104,6 +1107,7 @@ module "bucket" { | [parent](variables.tf#L184) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null | | [prefix](variables.tf#L194) | Optional prefix used to generate project id and name. | string | | null | | [project_create](variables.tf#L204) | Create project. When set to false, uses a data source to reference existing project. | bool | | true | +| [quotas](variables-quotas.tf#L17) | Service quota configuration. | map(object({…})) | | {} | | [service_config](variables.tf#L210) | Configure service API activation. | object({…}) | | {…} | | [service_encryption_key_ids](variables.tf#L222) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | map(list(string)) | | {} | | [service_perimeter_bridges](variables.tf#L229) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | list(string) | | null | @@ -1125,9 +1129,11 @@ module "bucket" { | [name](outputs.tf#L51) | Project name. | | | [number](outputs.tf#L63) | Project number. | | | [project_id](outputs.tf#L82) | Project id. | | -| [service_accounts](outputs.tf#L101) | Product robot service accounts in project. | | -| [services](outputs.tf#L117) | Service APIs to enabled in the project. | | -| [sink_writer_identities](outputs.tf#L126) | Writer identities created for each sink. | | -| [tag_keys](outputs.tf#L133) | Tag key resources. | | -| [tag_values](outputs.tf#L142) | Tag value resources. | | +| [quota_configs](outputs.tf#L101) | Quota configurations. | | +| [quotas](outputs.tf#L109) | Quota resources. | | +| [service_accounts](outputs.tf#L114) | Product robot service accounts in project. | | +| [services](outputs.tf#L130) | Service APIs to enabled in the project. | | +| [sink_writer_identities](outputs.tf#L139) | Writer identities created for each sink. | | +| [tag_keys](outputs.tf#L146) | Tag key resources. | | +| [tag_values](outputs.tf#L155) | Tag value resources. | | From 308d95873ea979c46edef1eaf2c528cf3f8a44b6 Mon Sep 17 00:00:00 2001 From: Ludo Date: Wed, 10 Apr 2024 13:28:27 +0200 Subject: [PATCH 3/7] better outputs --- modules/project/README.md | 12 ++++++------ modules/project/outputs.tf | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/project/README.md b/modules/project/README.md index 63d683e3bd..f3f5d47c18 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -1130,10 +1130,10 @@ module "bucket" { | [number](outputs.tf#L63) | Project number. | | | [project_id](outputs.tf#L82) | Project id. | | | [quota_configs](outputs.tf#L101) | Quota configurations. | | -| [quotas](outputs.tf#L109) | Quota resources. | | -| [service_accounts](outputs.tf#L114) | Product robot service accounts in project. | | -| [services](outputs.tf#L130) | Service APIs to enabled in the project. | | -| [sink_writer_identities](outputs.tf#L139) | Writer identities created for each sink. | | -| [tag_keys](outputs.tf#L146) | Tag key resources. | | -| [tag_values](outputs.tf#L155) | Tag value resources. | | +| [quotas](outputs.tf#L112) | Quota resources. | | +| [service_accounts](outputs.tf#L117) | Product robot service accounts in project. | | +| [services](outputs.tf#L133) | Service APIs to enabled in the project. | | +| [sink_writer_identities](outputs.tf#L142) | Writer identities created for each sink. | | +| [tag_keys](outputs.tf#L149) | Tag key resources. | | +| [tag_values](outputs.tf#L158) | Tag value resources. | | diff --git a/modules/project/outputs.tf b/modules/project/outputs.tf index 511be50655..e7d0d81063 100644 --- a/modules/project/outputs.tf +++ b/modules/project/outputs.tf @@ -102,7 +102,10 @@ output "quota_configs" { description = "Quota configurations." value = { for k, v in google_cloud_quotas_quota_preference.default : - "${v.service}/${v.quota_id}" => v.quota_config.0.preferred_value + "${v.service}/${v.quota_id}" => { + granted = v.quota_config.0.granted_value + preferred = v.quota_config.0.preferred_value + } } } From 9239ceca6245b2c92cfecdeabd1febe2b3f53039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Wed, 10 Apr 2024 13:08:27 +0000 Subject: [PATCH 4/7] Ensure keys in `quota_configs` are unique --- modules/project/outputs.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/project/outputs.tf b/modules/project/outputs.tf index e7d0d81063..c81b80728b 100644 --- a/modules/project/outputs.tf +++ b/modules/project/outputs.tf @@ -102,7 +102,7 @@ output "quota_configs" { description = "Quota configurations." value = { for k, v in google_cloud_quotas_quota_preference.default : - "${v.service}/${v.quota_id}" => { + k => { granted = v.quota_config.0.granted_value preferred = v.quota_config.0.preferred_value } From f1f7b1013575fa0aea6f2c91cd71b4a6e2593079 Mon Sep 17 00:00:00 2001 From: Ludo Date: Wed, 10 Apr 2024 15:10:43 +0200 Subject: [PATCH 5/7] update fast tests --- tests/fast/stages/s0_bootstrap/checklist.yaml | 4 ++-- tests/fast/stages/s0_bootstrap/simple.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/fast/stages/s0_bootstrap/checklist.yaml b/tests/fast/stages/s0_bootstrap/checklist.yaml index 8e4022c30e..9924f3bb50 100644 --- a/tests/fast/stages/s0_bootstrap/checklist.yaml +++ b/tests/fast/stages/s0_bootstrap/checklist.yaml @@ -369,7 +369,7 @@ counts: google_project: 3 google_project_iam_binding: 19 google_project_iam_member: 6 - google_project_service: 30 + google_project_service: 31 google_project_service_identity: 4 google_service_account: 4 google_service_account_iam_binding: 2 @@ -381,4 +381,4 @@ counts: google_tags_tag_key: 1 google_tags_tag_value: 1 modules: 17 - resources: 195 + resources: 196 diff --git a/tests/fast/stages/s0_bootstrap/simple.yaml b/tests/fast/stages/s0_bootstrap/simple.yaml index 3851a07618..1ac3e88070 100644 --- a/tests/fast/stages/s0_bootstrap/simple.yaml +++ b/tests/fast/stages/s0_bootstrap/simple.yaml @@ -48,7 +48,7 @@ counts: google_project: 3 google_project_iam_binding: 19 google_project_iam_member: 6 - google_project_service: 30 + google_project_service: 31 google_project_service_identity: 4 google_service_account: 4 google_service_account_iam_binding: 2 @@ -61,7 +61,7 @@ counts: google_tags_tag_value: 1 local_file: 7 modules: 16 - resources: 186 + resources: 187 outputs: custom_roles: From 6e1c4ad723a3d0832245a6c89854beb37b4a7ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Wed, 10 Apr 2024 14:34:57 +0000 Subject: [PATCH 6/7] Make quota E2E testable --- modules/project/README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/modules/project/README.md b/modules/project/README.md index f3f5d47c18..00c9be53f9 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -804,21 +804,28 @@ Project and regional quotas can be managed via the `quotas` variable, but requir ```hcl module "project" { - source = "./fabric/modules/project" - name = "project" + source = "./fabric/modules/project" + name = "project" + billing_account = var.billing_account_id + parent = var.folder_id + prefix = var.prefix quotas = { cpus-ew8 = { service = "compute.googleapis.com" quota_id = "CPUS-per-project-region" contact_email = "user@example.com" - preferred_value = 80 + preferred_value = 321 dimensions = { region = "europe-west8" } } } + services = [ + "cloudquotas.googleapis.com", + "compute.googleapis.com" + ] } -# tftest modules=1 resources=2 +# tftest modules=1 resources=4 e2e ``` ## Outputs From 0606015185d70bcf3998dd824fe15baad5ed3486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Wed, 10 Apr 2024 16:53:23 +0000 Subject: [PATCH 7/7] Remove quota project caveat --- modules/project/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/project/README.md b/modules/project/README.md index 00c9be53f9..e5f4042f5a 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -800,7 +800,7 @@ includedPermissions: ## Quotas -Project and regional quotas can be managed via the `quotas` variable, but require configuring the quota project used by the Terraform provider. This can be done at the provider level via the `user_project_override` and `billing_project` [provider configurations](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference.html#user_project_override). Note that some module functionality will break with this configuration, so we recommend [definining an alias](https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations) for the Google provider changing module so that the quota resource uses it. +Project and regional quotas can be managed via the `quotas` variable. ```hcl module "project" {