diff --git a/README.md b/README.md index 8061f777c..bdd0fc203 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Currently available modules: - **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [COS container](./modules/cloud-config-container/cos-generic-metadata/) (coredns, mysql, onprem, squid), [GKE cluster](./modules/gke-cluster-standard), [GKE hub](./modules/gke-hub), [GKE nodepool](./modules/gke-nodepool), [GCVE private cloud](./modules/gcve-private-cloud) - **data** - [AlloyDB instance](./modules/alloydb), [Analytics Hub](./modules/analytics-hub), [BigQuery dataset](./modules/bigquery-dataset), [Biglake Catalog](./modules/biglake-catalog), [Bigtable instance](./modules/bigtable-instance), [Dataplex](./modules/dataplex), [Dataplex Aspect Types](./modules/dataplex-aspect-types/), [Dataplex DataScan](./modules/dataplex-datascan), [Cloud SQL instance](./modules/cloudsql-instance), [Spanner instance](./modules/spanner-instance), [Firestore](./modules/firestore), [Data Catalog Policy Tag](./modules/data-catalog-policy-tag), [Data Catalog Tag](./modules/data-catalog-tag), [Data Catalog Tag Template](./modules/data-catalog-tag-template), [Datafusion](./modules/datafusion), [Dataproc](./modules/dataproc), [GCS](./modules/gcs), [Pub/Sub](./modules/pubsub), [Dataform Repository](./modules/dataform-repository/), [Looker Core](./modules/looker-core) - **AI** - [AI Applications](./modules/ai-applications/README.md) -- **development** - [API Gateway](./modules/api-gateway), [Apigee](./modules/apigee), [Artifact Registry](./modules/artifact-registry), [Container Registry](./modules/container-registry), [Cloud Source Repository](./modules/source-repository), [Secure Source Manager instance](./modules/secure-source-manager-instance), [Workstation cluster](./modules/workstation-cluster) +- **development** - [API Gateway](./modules/api-gateway), [Apigee](./modules/apigee), [Artifact Registry](./modules/artifact-registry), [Container Registry](./modules/container-registry), [Cloud Source Repository](./modules/source-repository), [Cloud Deploy](./modules/cloud-deploy), [Secure Source Manager instance](./modules/secure-source-manager-instance), [Workstation cluster](./modules/workstation-cluster) - **security** - [Binauthz](./modules/binauthz/), [Certificate Authority Service (CAS)](./modules/certificate-authority-service), [KMS](./modules/kms), [SecretManager](./modules/secret-manager), [VPC Service Control](./modules/vpc-sc), [Certificate Manager](./modules/certificate-manager/) - **serverless** - [Cloud Function v1](./modules/cloud-function-v1), [Cloud Function v2](./modules/cloud-function-v2), [Cloud Run](./modules/cloud-run), [Cloud Run v2](./modules/cloud-run-v2) diff --git a/fast/stages/1-vpcsc/main.tf b/fast/stages/1-vpcsc/main.tf index 827c69fee..f3a29e4b3 100644 --- a/fast/stages/1-vpcsc/main.tf +++ b/fast/stages/1-vpcsc/main.tf @@ -43,6 +43,7 @@ module "vpc-sc-discovery" { ignore_folders = var.resource_discovery.ignore_folders ignore_projects = var.resource_discovery.ignore_projects include_projects = var.resource_discovery.include_projects + query = "state:ACTIVE" } module "vpc-sc" { diff --git a/modules/README.md b/modules/README.md index cb194698c..7529d614d 100644 --- a/modules/README.md +++ b/modules/README.md @@ -112,6 +112,7 @@ These modules are used in the examples included in this repository. If you are u - [Artifact Registry](./artifact-registry) - [Container Registry](./container-registry) - [Cloud Source Repository](./source-repository) +- [Cloud Deploy](./cloud-deploy) - [Secure Source Manager instance](./secure-source-manager-instance) - [Workstation cluster](./workstation-cluster) diff --git a/modules/ai-applications/README.md b/modules/ai-applications/README.md index c31e09e1e..c9abd12c1 100644 --- a/modules/ai-applications/README.md +++ b/modules/ai-applications/README.md @@ -236,8 +236,8 @@ module "ai-applications" { |---|---|:---:|:---:|:---:| | [name](variables.tf#L159) | The name of the resources. | string | ✓ | | | [project_id](variables.tf#L165) | The ID of the project where the data stores and the agents will be created. | string | ✓ | | -| [data_stores_configs](variables.tf#L17) | The ai-applications datastore configurations. | map(object({…})) | | {} | -| [engines_configs](variables.tf#L112) | The ai-applications engines configurations. | map(object({…})) | | {} | +| [data_stores_configs](variables.tf#L17) | The ai-applications datastore configurations. | map(object({…})) | | {} | +| [engines_configs](variables.tf#L112) | The ai-applications engines configurations. | map(object({…})) | | {} | | [location](variables.tf#L153) | Location where the data stores and agents will be created. | string | | "global" | ## Outputs diff --git a/modules/ai-applications/engines.tf b/modules/ai-applications/engines.tf index 4c84f9b79..02b69cc74 100644 --- a/modules/ai-applications/engines.tf +++ b/modules/ai-applications/engines.tf @@ -56,8 +56,12 @@ resource "google_discovery_engine_chat_engine" "default" { } } - common_config { - company_name = each.value.chat_engine_config.company_name + dynamic "common_config" { + for_each = each.value.chat_engine_config.company_name == null ? [] : [""] + + content { + company_name = each.value.chat_engine_config.company_name + } } } diff --git a/modules/ai-applications/main.tf b/modules/ai-applications/main.tf index 49ec255cb..1443aef67 100644 --- a/modules/ai-applications/main.tf +++ b/modules/ai-applications/main.tf @@ -123,7 +123,7 @@ resource "google_discovery_engine_data_store" "default" { } dynamic "parsing_config_overrides" { - for_each = each.value.document_processing_config.parsing_config_overrides + for_each = coalesce(each.value.document_processing_config.parsing_config_overrides, {}) content { file_type = parsing_config_overrides.key diff --git a/modules/ai-applications/variables.tf b/modules/ai-applications/variables.tf index 6739b44ed..37ce13efa 100644 --- a/modules/ai-applications/variables.tf +++ b/modules/ai-applications/variables.tf @@ -39,13 +39,13 @@ variable "data_stores_configs" { })) })) # Accepted keys: docx, html, pdf - parsing_config_overrides = map(object({ + parsing_config_overrides = optional(map(object({ digital_parsing_config = optional(bool) layout_parsing_config = optional(bool) ocr_parsing_config = optional(object({ use_native_text = optional(bool) })) - })) + }))) })) industry_vertical = optional(string, "GENERIC") json_schema = optional(string) @@ -115,7 +115,7 @@ variable "engines_configs" { data_store_ids = list(string) collection_id = optional(string, "default_collection") chat_engine_config = optional(object({ - allow_cross_region = optional(bool) + allow_cross_region = optional(bool, null) business = optional(string) company_name = optional(string) default_language_code = optional(string) diff --git a/modules/cloud-deploy/README.md b/modules/cloud-deploy/README.md new file mode 100644 index 000000000..268e53dcc --- /dev/null +++ b/modules/cloud-deploy/README.md @@ -0,0 +1,454 @@ +# Cloud Deploy Module + +Cloud Deploy Module for creating and managing Delivery Pipelines, Targets, Automations, Deploy Policies and resource-level IAM roles. + + +- [Limitations](#limitations) +- [Examples](#examples) +- [Single Target Canary Deployment](#single-target-canary-deployment) +- [Single Target Canary Deployment with Custom Traffic Limits](#single-target-canary-deployment-with-custom-traffic-limits) +- [Single Target Canary Deployment with Verification](#single-target-canary-deployment-with-verification) +- [Delivery Pipeline with Existing Target](#delivery-pipeline-with-existing-target) +- [Multiple Targets in Serial Deployment](#multiple-targets-in-serial-deployment) +- [Multi Target Multi Project Deployment](#multi-target-multi-project-deployment) +- [Multi Target with Serial and Parallel deployment](#multi-target-with-serial-and-parallel-deployment) +- [Automation for Delivery Pipelines](#automation-for-delivery-pipelines) +- [Deployment Policy](#deployment-policy) +- [IAM for Delivery Pipeline and Target resource level](#iam-for-delivery-pipeline-and-target-resource-level) +- [Variables](#variables) +- [Outputs](#outputs) + + +## Limitations +> [!WARNING] +> Currently this module only supports Cloud Run deployments and does not include GKE or Custom Target deployments. + +## Examples + +## Single Target Canary Deployment + +This deploys a Cloud Deploy Delivery Pipeline with a single target using the Canary deployment strategy, which by default routes 10% of traffic initially and upon success, shifts to 100% (making it the stable revision). By default `strategy = "STANDARD"` is set, to use canary strategy this needs to be changed to `strategy = "CANARY"`. + + +```hcl +module "cloud_deploy" { + source = "./fabric/modules/cloud-deploy" + project_id = var.project_id + region = var.region + name = "deployment-pipeline" + + targets = [ + { + name = "dev-target" + description = "Dev Target" + profiles = ["dev"] + strategy = "CANARY" + cloud_run_configs = { + automatic_traffic_control = true + } + } + ] +} +# tftest modules=1 resources=2 +``` + +## Single Target Canary Deployment with Custom Traffic Limits + +This deploys a Cloud Deploy Delivery Pipeline with a single target with the Canary deployment strategy. `deployment_percentages` can be set to specify the traffic stages that would be applied during the canary deployment. It accepts integer values in ascending order and between 0 to 99. + + +```hcl +module "cloud_deploy" { + source = "./fabric/modules/cloud-deploy" + project_id = var.project_id + region = var.region + name = "deployment-pipeline" + + + targets = [ + { + name = "dev-target" + description = "Dev Target" + profiles = ["dev"] + strategy = "CANARY" + deployment_percentages = [10, 50, 70] + cloud_run_configs = { + automatic_traffic_control = true + } + } + ] +} +# tftest modules=1 resources=2 +``` + +## Single Target Canary Deployment with Verification + +This deployments enables the rollout to have a verification step by setting `verify = true`. The verification step and configurations need to be passed within the skaffold file. + +```hcl +module "cloud_deploy" { + source = "./fabric/modules/cloud-deploy" + project_id = var.project_id + region = var.region + name = "deployment-pipeline" + + + targets = [ + { + name = "dev-target" + description = "Dev Target" + profiles = ["dev"] + strategy = "CANARY" + verify = true + cloud_run_configs = { + automatic_traffic_control = true + } + } + ] +} +# tftest modules=1 resources=2 +``` + +## Delivery Pipeline with Existing Target + +This deployment demonstrates the ability to create a delivery pipeline by reusing existing targets. By default a `create_target = true` is set, creating and assigning a target to the delivery pipeline. Setting it to false directs the code to assign the target to the delivery pipeline and skip its creation during execution. + +```hcl +module "cloud_deploy" { + source = "./fabric/modules/cloud-deploy" + project_id = var.project_id + region = var.region + name = "deployment-pipeline" + + + targets = [ + { + create_target = false + name = "dev-target" + description = "Dev Target" + profiles = ["dev"] + strategy = "CANARY" + cloud_run_configs = { + automatic_traffic_control = true + } + } + ] +} +# tftest modules=1 resources=1 +``` + +## Multiple Targets in Serial Deployment + +Cloud Deployment supports deployments to multiple targets. This example shows how to create 3 targets and to set them in sequence. +The sequence of deployment is defined by the sequence of the target configuration object within the list. `require_approval` can be set to true for any target that requires an approval prior to its deployment/rollout. + + +```hcl +module "cloud_deploy" { + source = "./fabric/modules/cloud-deploy" + project_id = var.project_id + region = var.region + name = "deployment-pipeline" + + + targets = [ + { + name = "dev-target" + description = "Dev Target" + profiles = ["dev"] + strategy = "CANARY" + cloud_run_configs = { + automatic_traffic_control = true + } + }, + { + name = "qa-target" + description = "QA Target" + profiles = ["qa"] + strategy = "CANARY" + cloud_run_configs = { + automatic_traffic_control = true + } + }, + { + name = "prod-target" + description = "Prod Target" + profiles = ["prod"] + require_approval = true + strategy = "CANARY" + cloud_run_configs = { + automatic_traffic_control = true + } + } + ] +} +# tftest modules=1 resources=4 +``` + + +## Multi Target Multi Project Deployment + +Targets in this deployment can deploy to different projects. For instance, `qa-target` deploys to a separate `project_id` and `region`. To direct Cloud Run deployments to a different project, specify the `project_id` and `region` under `cloud_run_configs`. By default, Cloud Run services will use the target's own `project_id` and `region`. + + +```hcl +module "cloud_deploy" { + source = "./fabric/modules/cloud-deploy" + project_id = var.project_id + region = var.region + name = "deployment-pipeline" + + + targets = [ + { + name = "dev-target" + description = "Dev Target" + profiles = ["dev"] + strategy = "CANARY" + cloud_run_configs = { + automatic_traffic_control = true + } + }, + { + name = "qa-target" + description = "QA Target" + profiles = ["qa"] + strategy = "CANARY" + cloud_run_configs = { + project_id = "" + region = "" + automatic_traffic_control = true + } + }, + { + name = "prod-target" + description = "Prod Target" + profiles = ["prod"] + require_approval = true + strategy = "CANARY" + cloud_run_configs = { + automatic_traffic_control = true + } + } + ] +} +# tftest modules=1 resources=4 +``` + +## Multi Target with Serial and Parallel deployment + +Cloud Deploy allows deploying to targets in a serial and parallel order. By defining a multi-target target configuration using `multi_target_target_ids` cloud deploy would execute the deployments in parallel. `require_approval` should only be applied to the multi-target target configuration and not the the child targets. As the child targets would execute within the multi-target target configuration, they are excluded from being directly assigned in the serial sequence of the delivery pipeline, using `exclude_from_pipeline = true`. + +```hcl +module "cloud_deploy" { + source = "./fabric/modules/cloud-deploy" + project_id = var.project_id + region = var.region + name = "deployment-pipeline" + + + targets = [ + { + name = "dev-target" + description = "Dev Target" + profiles = ["dev"] + strategy = "CANARY" + cloud_run_configs = { + automatic_traffic_control = true + } + }, + { + name = "multi-qa-target" + description = "Multi QA target" + profiles = ["multi-qa"] + multi_target_target_ids = ["qa-target-1", "qa-target-2"] + strategy = "STANDARD" + }, + { + exclude_from_pipeline = true + name = "qa-target-1" + description = "QA target-1" + profiles = ["qa-1"] + strategy = "CANARY" + cloud_run_configs = { + automatic_traffic_control = true + } + }, + { + exclude_from_pipeline = true + name = "qa-target-2" + description = "QA target-2" + profiles = ["qa-2"] + strategy = "CANARY" + cloud_run_configs = { + automatic_traffic_control = true + } + } + ] +} +# tftest modules=1 resources=5 +``` + +## Automation for Delivery Pipelines + +This deployment incorporates automations that are supported within a delivery pipeline. If automations are defined at least 1 rule needs to be specified. Rules are defined as `"automation-name" = { }` format. Multiple automations can be defined and multiple rules can be specified within an automation. A `service_account` can be provided to execute the automation using the defined service account. If this is missing it defaults to the compute engine default service account (`-compute@developer.gserviceaccount.com`). + +```hcl +module "cloud_deploy" { + source = "./fabric/modules/cloud-deploy" + project_id = var.project_id + region = var.region + name = "deployment-pipeline" + + automations = { + "advance-rollout" = { + description = "advance_rollout_rule" + service_account = "@.iam.gserviceaccount.com" + advance_rollout_rule = { + source_phases = ["canary"] + wait = "200s" + } + }, + "repair-rollout" = { + description = "repair_rollout_rule" + service_account = "@.iam.gserviceaccount.com" + repair_rollout_rule = { + jobs = ["predeploy", "deploy", "postdeploy", "verify"] + phases = ["canary-10", "stable"] + rollback = { + destination_phase = "stable" + } + } + } + } + + targets = [ + { + name = "dev-target" + description = "Dev Target" + profiles = ["dev"] + strategy = "CANARY" + cloud_run_configs = { + automatic_traffic_control = true + } + } + ] +} +# tftest modules=1 resources=4 +``` + +## Deployment Policy + +This example provides a way to define a deployment policy along with the delivery pipeline. Each deploy policy can be defined as `"deploy_policy_name" = { }` format. Rollout restrictions are defined as `"restriction_name" = { }` format. +By default, the deployment policy defined below applies to all delivery pipelines. If this requires a change, modify the selector option. Selector types supported are: "DELIVERY_PIPELINE" and "TARGET". + +```hcl +module "cloud_deploy" { + source = "./fabric/modules/cloud-deploy" + project_id = var.project_id + region = var.region + name = "deployment-pipeline" + + targets = [ + { + name = "dev-target" + description = "Dev Target" + profiles = ["dev"] + strategy = "CANARY" + cloud_run_configs = { + automatic_traffic_control = true + } + } + ] + + deploy_policies = { + "deploy-policy" = { + selectors = [{ + id = "*" + type = "DELIVERY_PIPELINE" + }] + rollout_restrictions = { + "restriction-1" = { + time_zone = "Australia/Melbourne" + weekly_windows = [{ + days_of_week = ["MONDAY", "TUESDAY"] + + start_time = { + hours = "10" + minutes = "30" + seconds = "00" + nanos = "00" + } + + end_time = { + hours = "12" + minutes = "30" + seconds = "00" + nanos = "00" + } + }] + } } + } + } +} +# tftest modules=1 resources=3 +``` + +## IAM for Delivery Pipeline and Target resource level + +This example specifies the option to set IAM roles at the Delivery Pipeline and Target resource level. IAM bindings support the usual syntax. +`iam`, `iam_bindings`, `iam_bindings_additive`, `iam_by_principals` are supported for delivery pipelines and targets. + +```hcl +module "cloud_run" { + source = "./fabric/modules/cloud-deploy" + project_id = var.project_id + region = var.region + name = "deployment-pipeline" + + iam = { "roles/clouddeploy.developer" = ["user:allUsers"] } + + targets = [ + { + name = "dev-target" + description = "Dev Target" + profiles = ["dev"] + strategy = "CANARY" + cloud_run_configs = { + automatic_traffic_control = true + } + iam = { "roles/clouddeploy.operator" = ["user:allUsers"] } + } + ] +} +# tftest modules=1 resources=4 +``` + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [name](variables.tf#L191) | Cloud Deploy Delivery Pipeline name. | string | ✓ | | +| [project_id](variables.tf#L201) | Project id used for resources, if not explicitly specified. | string | ✓ | | +| [region](variables.tf#L206) | Region used for resources, if not explicitly specified. | string | ✓ | | +| [annotations](variables.tf#L17) | Resource annotations. | map(string) | | {} | +| [automations](variables.tf#L24) | Configuration for automations associated with the deployment pipeline in a name => attributes format. | map(object({…})) | | {} | +| [deploy_policies](variables.tf#L84) | Configurations for Deployment Policies in a name => attributes format. | map(object({…})) | | {} | +| [description](variables.tf#L165) | Cloud Deploy Delivery Pipeline description. | string | | "Terraform managed." | +| [iam](variables-iam.tf#L17) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | +| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | +| [iam_by_principals](variables-iam.tf#L54) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | +| [labels](variables.tf#L176) | Cloud Deploy Delivery Pipeline resource labels. | map(string) | | {} | +| [suspended](variables.tf#L211) | Configuration to suspend a delivery pipeline. | bool | | false | +| [targets](variables.tf#L218) | Configuration for new targets associated with the delivery pipeline in a list format. Order of the targets are defined by the order within the list. | list(object({…})) | | [] | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [automation_ids](outputs.tf#L18) | Automation ids. | | +| [deploy_policy_ids](outputs.tf#L23) | Deploy Policy ids. | | +| [pipeline_id](outputs.tf#L28) | Delivery pipeline id. | | +| [target_ids](outputs.tf#L33) | Target ids. | | + diff --git a/modules/cloud-deploy/automation.tf b/modules/cloud-deploy/automation.tf new file mode 100644 index 000000000..aaff9dd30 --- /dev/null +++ b/modules/cloud-deploy/automation.tf @@ -0,0 +1,134 @@ +/** + * Copyright 2025 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_clouddeploy_automation" "default" { + for_each = local.validated_automations + + project = coalesce(each.value.project_id, var.project_id) + location = coalesce(each.value.region, var.region) + name = each.key + annotations = each.value.annotations + delivery_pipeline = google_clouddeploy_delivery_pipeline.default.name + description = each.value.description + labels = each.value.labels + service_account = coalesce(each.value.service_account, local.compute_default_service_account) + suspended = each.value.suspended + + dynamic "rules" { + for_each = each.value.advance_rollout_rule == null ? [] : [""] + + content { + dynamic "advance_rollout_rule" { + for_each = each.value.advance_rollout_rule == null ? [] : [""] + + content { + id = each.value.advance_rollout_rule.id + source_phases = each.value.advance_rollout_rule.source_phases + wait = each.value.advance_rollout_rule.wait + } + } + } + } + + dynamic "rules" { + for_each = each.value.promote_release_rule == null ? [] : [""] + + content { + dynamic "promote_release_rule" { + for_each = each.value.promote_release_rule == null ? [] : [""] + + content { + id = each.value.promote_release_rule.id + destination_phase = each.value.promote_release_rule.destination_target_id + destination_target_id = each.value.promote_release_rule.destination_target_id + wait = each.value.promote_release_rule.wait + } + } + } + } + + dynamic "rules" { + for_each = each.value.repair_rollout_rule == null ? [] : [""] + + content { + dynamic "repair_rollout_rule" { + for_each = each.value.repair_rollout_rule == null ? [] : [""] + + content { + id = each.value.repair_rollout_rule.id + jobs = each.value.repair_rollout_rule.jobs + phases = each.value.repair_rollout_rule.phases + + dynamic "repair_phases" { + for_each = each.value.repair_rollout_rule.retry == null ? [] : [""] + + content { + retry { + attempts = each.value.repair_rollout_rule.retry.attempts + backoff_mode = each.value.repair_rollout_rule.retry.backoff_mode + wait = each.value.repair_rollout_rule.retry.wait + } + } + } + dynamic "repair_phases" { + for_each = each.value.repair_rollout_rule.rollback == null ? [] : [""] + + content { + rollback { + destination_phase = each.value.repair_rollout_rule.rollback.destination_phase + disable_rollback_if_rollout_pending = each.value.repair_rollout_rule.rollback.disable_rollback_if_rollout_pending + } + } + } + } + } + } + } + + dynamic "rules" { + for_each = each.value.timed_promote_release_rule == null ? [] : [""] + + content { + dynamic "timed_promote_release_rule" { + for_each = each.value.timed_promote_release_rule == null ? [] : [""] + + content { + id = each.value.timed_promote_release_rule.id + destination_phase = each.value.timed_promote_release_rule.destination_phase + destination_target_id = each.value.timed_promote_release_rule.destination_target_id + schedule = each.value.timed_promote_release_rule.schedule + time_zone = each.value.timed_promote_release_rule.time_zone + } + } + } + } + + selector { + dynamic "targets" { + for_each = { + for k, v in each.value.selector : + k => v + } + iterator = et + + content { + id = et.value.id + labels = et.value.labels + } + } + } +} \ No newline at end of file diff --git a/modules/cloud-deploy/delivery-pipeline.tf b/modules/cloud-deploy/delivery-pipeline.tf new file mode 100644 index 000000000..5abdef68f --- /dev/null +++ b/modules/cloud-deploy/delivery-pipeline.tf @@ -0,0 +1,158 @@ +/** + * Copyright 2025 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_clouddeploy_delivery_pipeline" "default" { + project = var.project_id + location = var.region + name = var.name + annotations = var.annotations + description = var.description + labels = var.labels + suspended = var.suspended + + dynamic "serial_pipeline" { + for_each = lower(local.pipeline_type) == "serial" ? [""] : [] + + content { + dynamic "stages" { + for_each = { + for k, v in var.targets : + k => v if v.exclude_from_pipeline == false + } + iterator = et + + content { + profiles = et.value.profiles + target_id = et.value.name + + dynamic "deploy_parameters" { + for_each = { + for k, v in et.value.delivery_pipeline_deploy_parameters : + k => v + } + iterator = edp + + content { + match_target_labels = edp.value.matching_target_labels + values = edp.value.values + } + } + + dynamic "strategy" { + for_each = et.value.strategy == null ? [] : [""] + + content { + dynamic "canary" { + for_each = upper(et.value.strategy) == "CANARY" ? [""] : [] + + content { + canary_deployment { + percentages = et.value.deployment_percentages + verify = et.value.verify + + dynamic "postdeploy" { + for_each = et.value.postdeploy_actions == null ? [] : [""] + content { + actions = et.value.postdeploy_actions + } + } + + dynamic "predeploy" { + for_each = et.value.predeploy_actions == null ? [] : [""] + content { + actions = et.value.predeploy_actions + } + } + } + + dynamic "custom_canary_deployment" { + for_each = length(et.value.custom_canary_phase_configs) > 0 ? [""] : [] + + content { + dynamic "phase_configs" { + for_each = et.value.custom_canary_phase_configs + iterator = epc + + content { + percentage = epc.value.percentage + phase_id = epc.key + verify = epc.value.verify + + dynamic "postdeploy" { + for_each = epc.value.postdeploy_actions == null ? [] : [""] + + content { + actions = epc.value.postdeploy_actions + } + } + + dynamic "predeploy" { + for_each = epc.value.predeploy_actions == null ? [] : [""] + + content { + actions = epc.value.predeploy_actions + } + } + } + } + } + } + + runtime_config { + dynamic "cloud_run" { + for_each = et.value.cloud_run_configs == null ? [] : [""] + + content { + automatic_traffic_control = et.value.cloud_run_configs.automatic_traffic_control + canary_revision_tags = et.value.cloud_run_configs.canary_revision_tags + prior_revision_tags = et.value.cloud_run_configs.prior_revision_tags + stable_revision_tags = et.value.cloud_run_configs.stable_revision_tags + } + } + } + } + } + + dynamic "standard" { + for_each = upper(et.value.strategy) == "STANDARD" ? [""] : [] + + content { + verify = et.value.verify + + dynamic "postdeploy" { + for_each = et.value.postdeploy_actions == null ? [] : [""] + content { + actions = et.value.postdeploy_actions + } + } + + dynamic "predeploy" { + for_each = et.value.predeploy_actions == null ? [] : [""] + content { + actions = et.value.predeploy_actions + } + } + } + } + } + } + } + } + } + } +} + diff --git a/modules/cloud-deploy/deploy-policy.tf b/modules/cloud-deploy/deploy-policy.tf new file mode 100644 index 000000000..0cb6db85c --- /dev/null +++ b/modules/cloud-deploy/deploy-policy.tf @@ -0,0 +1,159 @@ +/** + * Copyright 2025 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_clouddeploy_deploy_policy" "default" { + for_each = var.deploy_policies + + project = coalesce(each.value.project_id, var.project_id) + location = coalesce(each.value.region, var.region) + name = each.key + annotations = each.value.annotations + description = each.value.description + labels = each.value.labels + suspended = each.value.suspended + + dynamic "rules" { + for_each = each.value.rollout_restrictions + iterator = er + + content { + rollout_restriction { + id = er.key + actions = er.value.actions + invokers = er.value.invokers + + time_windows { + time_zone = er.value.time_zone + + dynamic "one_time_windows" { + for_each = { + for k, v in er.value.one_time_windows : + k => v + } + iterator = eotw + + content { + dynamic "end_date" { + for_each = eotw.value.end_date == null ? [] : [""] + + content { + day = eotw.value.end_date.day + month = eotw.value.end_date.month + year = eotw.value.end_date.year + } + } + + dynamic "end_time" { + for_each = eotw.value.end_time == null ? [] : [""] + + content { + hours = eotw.value.end_time.hours + minutes = eotw.value.end_time.minutes + seconds = eotw.value.end_time.seconds + nanos = eotw.value.end_time.nanos + } + } + + dynamic "start_date" { + for_each = eotw.value.start_date == null ? [] : [""] + + content { + day = eotw.value.start_date.day + month = eotw.value.start_date.month + year = eotw.value.start_date.year + } + } + + dynamic "start_time" { + for_each = eotw.value.start_time == null ? [] : [""] + + content { + hours = eotw.value.start_time.hours + minutes = eotw.value.start_time.minutes + seconds = eotw.value.start_time.seconds + nanos = eotw.value.start_time.nanos + } + } + } + } + + dynamic "weekly_windows" { + for_each = { + for k, v in er.value.weekly_windows : + k => v + } + iterator = eww + + content { + days_of_week = eww.value.days_of_week + + dynamic "end_time" { + for_each = eww.value.end_time == null ? [] : [""] + + content { + hours = eww.value.end_time.hours + minutes = eww.value.end_time.minutes + seconds = eww.value.end_time.seconds + nanos = eww.value.end_time.nanos + } + } + + dynamic "start_time" { + for_each = eww.value.start_time == null ? [] : [""] + + content { + hours = eww.value.start_time.hours + minutes = eww.value.start_time.minutes + seconds = eww.value.start_time.seconds + nanos = eww.value.start_time.nanos + } + } + } + } + } + } + } + } + + dynamic "selectors" { + for_each = { + for k, v in each.value.selectors : + k => v + } + iterator = es + + content { + dynamic "delivery_pipeline" { + for_each = upper(es.value.type) == "DELIVERY_PIPELINE" ? [""] : [] + + content { + id = es.value.id + labels = es.value.labels + } + } + + dynamic "target" { + for_each = upper(es.value.type) == "TARGET" ? [""] : [] + + content { + id = es.value.id + labels = es.value.labels + } + } + } + } +} \ No newline at end of file diff --git a/modules/cloud-deploy/iam.tf b/modules/cloud-deploy/iam.tf new file mode 100644 index 000000000..cb1c8533e --- /dev/null +++ b/modules/cloud-deploy/iam.tf @@ -0,0 +1,160 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +# tfdoc:file:description IAM bindings. + +locals { + _iam_principal_roles = distinct(flatten(values(var.iam_by_principals))) + _iam_principals = { + for r in local._iam_principal_roles : r => [ + for k, v in var.iam_by_principals : + k if try(index(v, r), null) != null + ] + } + _merge_target_iam = flatten([ + for kt, vt in var.targets : [ + for role in distinct(concat(keys(vt.iam), keys(local._target_iam_principals[vt.name]))) : + { + "project_id" = vt.project_id + "region" = vt.region + "name" = vt.name + "role" = role + "members" = concat( + try(vt.iam[role], []), + try(local._target_iam_principals[vt.name][role], []) + ) + } + ] + ]) + _target_iam_principal_roles = { for k, v in var.targets : v.name => distinct(flatten(values(v.iam_by_principals))) } + _target_iam_principals = { + for k, v in var.targets : v.name => { + for r in local._target_iam_principal_roles[v.name] : r => [ + for kp, vp in v.iam_by_principals : + kp if try(index(vp, r), null) != null + ] + } + } + iam = { + for role in distinct(concat(keys(var.iam), keys(local._iam_principals))) : + role => concat( + try(var.iam[role], []), + try(local._iam_principals[role], []) + ) + } + target_iam = { + for k, v in local._merge_target_iam : k => v + } + target_iam_bindings = merge([ + for k, v in var.targets : { + for ki, vi in v.iam_bindings : + "${ki}_${k}" => merge(vi, { "project_id" = v.project_id, "region" = v.region, "name" = v.name }) + } + ]...) + target_iam_bindings_additive = merge([ + for k, v in var.targets : { + for ki, vi in v.iam_bindings_additive : + "${ki}_${k}" => merge(vi, { "project_id" = v.project_id, "region" = v.region, "name" = v.name }) + } + ]...) +} + +resource "google_clouddeploy_delivery_pipeline_iam_binding" "authoritative" { + for_each = local.iam + project = var.project_id + location = var.region + name = var.name + role = each.key + members = each.value +} + +resource "google_clouddeploy_delivery_pipeline_iam_binding" "bindings" { + for_each = var.iam_bindings + project = var.project_id + location = var.region + name = var.name + role = each.value.role + members = each.value.members + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } +} + +resource "google_clouddeploy_delivery_pipeline_iam_member" "bindings" { + for_each = var.iam_bindings_additive + project = var.project_id + location = var.region + name = var.name + role = each.value.role + member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } +} + +resource "google_clouddeploy_target_iam_binding" "authoritative" { + for_each = local.target_iam + project = coalesce(each.value.project_id, var.project_id) + location = coalesce(each.value.region, var.region) + name = each.value.name + role = each.value.role + members = each.value.members +} + +resource "google_clouddeploy_target_iam_binding" "bindings" { + for_each = local.target_iam_bindings + project = coalesce(each.value.project_id, var.project_id) + location = coalesce(each.value.region, var.region) + name = each.value.name + role = each.value.role + members = each.value.members + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } +} + +resource "google_clouddeploy_target_iam_member" "bindings" { + for_each = local.target_iam_bindings_additive + project = coalesce(each.value.project_id, var.project_id) + location = coalesce(each.value.region, var.region) + name = each.value.name + role = each.value.role + member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } +} diff --git a/modules/cloud-deploy/main.tf b/modules/cloud-deploy/main.tf new file mode 100644 index 000000000..45675d594 --- /dev/null +++ b/modules/cloud-deploy/main.tf @@ -0,0 +1,29 @@ +/** + * Copyright 2025 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 { + compute_default_service_account = length(data.google_compute_default_service_account.default) > 0 ? data.google_compute_default_service_account.default[0].email : null + pipeline_type = "serial" + validated_automations = { + for k, v in var.automations : + k => v if v != null && (try(length(v.promote_release_rule), 0) > 0 || try(length(v.advance_rollout_rule), 0) > 0 || try(length(v.repair_rollout_rule), 0) > 0 || try(length(v.timed_promote_release_rule), 0) > 0) + } +} + +data "google_compute_default_service_account" "default" { + count = alltrue([for k, v in var.automations : v.service_account != null]) ? 0 : 1 + project = var.project_id +} \ No newline at end of file diff --git a/modules/cloud-deploy/outputs.tf b/modules/cloud-deploy/outputs.tf new file mode 100644 index 000000000..efa0bc2a3 --- /dev/null +++ b/modules/cloud-deploy/outputs.tf @@ -0,0 +1,37 @@ + +/** + * Copyright 2025 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 "automation_ids" { + description = "Automation ids." + value = values(google_clouddeploy_automation.default)[*].id +} + +output "deploy_policy_ids" { + description = "Deploy Policy ids." + value = values(google_clouddeploy_deploy_policy.default)[*].id +} + +output "pipeline_id" { + description = "Delivery pipeline id." + value = google_clouddeploy_delivery_pipeline.default.id +} + +output "target_ids" { + description = "Target ids." + value = values(google_clouddeploy_target.default)[*].id +} + diff --git a/modules/cloud-deploy/target.tf b/modules/cloud-deploy/target.tf new file mode 100644 index 000000000..227b49773 --- /dev/null +++ b/modules/cloud-deploy/target.tf @@ -0,0 +1,58 @@ +/** + * Copyright 2025 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_clouddeploy_target" "default" { + for_each = { + for name, target in var.targets : + name => target if target.create_target == true + } + + project = coalesce(each.value.project_id, var.project_id) + location = coalesce(each.value.region, var.region) + name = each.value.name + annotations = each.value.annotations + deploy_parameters = each.value.target_deploy_parameters + description = each.value.description + labels = each.value.labels + require_approval = each.value.require_approval + + dynamic "execution_configs" { + for_each = each.value.execution_configs_usages == null && each.value.execution_configs_timeout == null ? [] : [""] + + content { + execution_timeout = each.value.execution_configs_timeout + usages = each.value.execution_configs_usages + } + } + + dynamic "multi_target" { + for_each = each.value.multi_target_target_ids == null ? [] : [""] + + content { + target_ids = each.value.multi_target_target_ids + } + } + + dynamic "run" { + for_each = each.value.cloud_run_configs == null ? [] : [""] + + content { + location = "projects/${coalesce(each.value.cloud_run_configs.project_id, each.value.project_id, var.project_id)}/locations/${coalesce(each.value.cloud_run_configs.region, each.value.region, var.region)}" + } + } +} + diff --git a/modules/cloud-deploy/variables-iam.tf b/modules/cloud-deploy/variables-iam.tf new file mode 100644 index 000000000..7e861d6bc --- /dev/null +++ b/modules/cloud-deploy/variables-iam.tf @@ -0,0 +1,59 @@ +/** + * Copyright 2025 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 "iam" { + description = "IAM bindings in {ROLE => [MEMBERS]} format." + type = map(list(string)) + default = {} + nullable = false +} + +variable "iam_bindings" { + description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary." + type = map(object({ + members = list(string) + role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })) + nullable = false + default = {} +} + +variable "iam_bindings_additive" { + description = "Individual additive IAM bindings. Keys are arbitrary." + type = map(object({ + member = string + role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })) + nullable = false + default = {} +} + +variable "iam_by_principals" { + description = "Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable." + type = map(list(string)) + default = {} + nullable = false +} diff --git a/modules/cloud-deploy/variables.tf b/modules/cloud-deploy/variables.tf new file mode 100644 index 000000000..763af1ab8 --- /dev/null +++ b/modules/cloud-deploy/variables.tf @@ -0,0 +1,297 @@ +/** + * Copyright 2025 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 "annotations" { + description = "Resource annotations." + type = map(string) + default = {} + nullable = false +} + +variable "automations" { + description = "Configuration for automations associated with the deployment pipeline in a name => attributes format." + type = map(object({ + project_id = optional(string, null) + region = optional(string, null) + annotations = optional(map(string)) + description = optional(string, null) + labels = optional(map(string)) + service_account = optional(string, null) + suspended = optional(bool, false) + advance_rollout_rule = optional(object({ + id = optional(string, "advance-rollout") + source_phases = optional(list(string), null) + wait = optional(string, null) + })) + promote_release_rule = optional(object({ + id = optional(string, "promote-release") + wait = optional(string, null) + destination_target_id = optional(string, null) + destination_phase = optional(string, null) + })) + repair_rollout_rule = optional(object({ + id = optional(string, "repair-rollout") + phases = optional(list(string), null) + jobs = optional(list(string), null) + retry = optional(object({ + attempts = optional(string, null) + wait = optional(string, null) + backoff_mode = optional(string, null) + })) + rollback = optional(object({ + destination_phase = optional(string, null) + disable_rollback_if_rollout_pending = optional(bool, true) + })) + })) + timed_promote_release_rule = optional(object({ + id = optional(string, "timed-promote-release") + destination_target_id = optional(string, null) + schedule = optional(string, null) + time_zone = optional(string, null) + destination_phase = optional(string, null) + })) + selector = optional(list(object({ + id = optional(string, "*") + labels = optional(map(string), {}) + })), [{ id = "*" }]) + })) + default = {} + nullable = false + validation { + condition = alltrue([ + for k, v in var.automations : + !(v.promote_release_rule == null && v.advance_rollout_rule == null && v.repair_rollout_rule == null && v.timed_promote_release_rule == null) + ]) + error_message = < 0 || length(vv.one_time_windows) > 0) ? 0 : 1]) == 0 + ]) + error_message = <= 0 && p < 100 : p >= 0 && p < 100 && p > v.deployment_percentages[i - 1] + ]) + ]) + error_message = "For canary strategy, deployment percentages must be in ascending order and each percentage must be between 0 and 99." + } +} diff --git a/modules/cloud-deploy/versions.tf b/modules/cloud-deploy/versions.tf new file mode 100644 index 000000000..0ebb55c97 --- /dev/null +++ b/modules/cloud-deploy/versions.tf @@ -0,0 +1,35 @@ +# Copyright 2025 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 +# +# https://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. + +# Fabric release: 40.1.0 + +terraform { + required_version = ">= 1.11.4" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 6.33.0, < 7.0.0" # tftest + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 6.33.0, < 7.0.0" # tftest + } + } + provider_meta "google" { + module_name = "google-pso-tool/cloud-foundation-fabric/modules/cloud-deploy:40.1.0-tf" + } + provider_meta "google-beta" { + module_name = "google-pso-tool/cloud-foundation-fabric/modules/cloud-deploy:40.1.0-tf" + } +} diff --git a/modules/cloud-deploy/versions.tofu b/modules/cloud-deploy/versions.tofu new file mode 100644 index 000000000..5d4839e8d --- /dev/null +++ b/modules/cloud-deploy/versions.tofu @@ -0,0 +1,35 @@ +# Copyright 2025 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 +# +# https://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. + +# Fabric release: 40.1.0 + +terraform { + required_version = ">= 1.9.0" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 6.33.0, < 7.0.0" # tftest + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 6.33.0, < 7.0.0" # tftest + } + } + provider_meta "google" { + module_name = "google-pso-tool/cloud-foundation-fabric/modules/cloud-deploy:40.1.0-tofu" + } + provider_meta "google-beta" { + module_name = "google-pso-tool/cloud-foundation-fabric/modules/cloud-deploy:40.1.0-tofu" + } +} diff --git a/modules/gke-cluster-standard/README.md b/modules/gke-cluster-standard/README.md index 2c8b9820b..fca8d922b 100644 --- a/modules/gke-cluster-standard/README.md +++ b/modules/gke-cluster-standard/README.md @@ -488,8 +488,8 @@ module "cluster-1" { |---|---|:---:|:---:|:---:| | [location](variables.tf#L269) | Cluster zone or region. | string | ✓ | | | [name](variables.tf#L384) | Cluster name. | string | ✓ | | -| [project_id](variables.tf#L418) | Cluster project id. | string | ✓ | | -| [vpc_config](variables.tf#L429) | VPC-level configuration. | object({…}) | ✓ | | +| [project_id](variables.tf#L435) | Cluster project id. | string | ✓ | | +| [vpc_config](variables.tf#L446) | VPC-level configuration. | object({…}) | ✓ | | | [access_config](variables.tf#L17) | Control plane endpoint and nodes access configurations. | object({…}) | | {} | | [backup_configs](variables.tf#L45) | Configuration for Backup for GKE. | object({…}) | | {} | | [cluster_autoscaling](variables.tf#L67) | Enable and configure limits for Node Auto-Provisioning with Cluster Autoscaler. | object({…}) | | null | @@ -507,7 +507,8 @@ module "cluster-1" { | [monitoring_config](variables.tf#L330) | Monitoring configuration. Google Cloud Managed Service for Prometheus is enabled by default. | object({…}) | | {} | | [node_config](variables.tf#L389) | Node-level configuration. | object({…}) | | {} | | [node_locations](variables.tf#L411) | Zones in which the cluster's nodes are located. | list(string) | | [] | -| [release_channel](variables.tf#L423) | Release channel for GKE upgrades. | string | | null | +| [node_pool_auto_config](variables.tf#L418) | Node pool configs that apply to auto-provisioned node pools in autopilot clusters and node auto-provisioning-enabled clusters. | object({…}) | | {} | +| [release_channel](variables.tf#L440) | Release channel for GKE upgrades. | string | | null | ## Outputs diff --git a/modules/gke-cluster-standard/main.tf b/modules/gke-cluster-standard/main.tf index ae2083bbb..51e53dd9e 100644 --- a/modules/gke-cluster-standard/main.tf +++ b/modules/gke-cluster-standard/main.tf @@ -88,6 +88,18 @@ resource "google_container_cluster" "cluster" { } } } + node_pool_auto_config { + network_tags { + tags = var.node_pool_auto_config.network_tags + } + resource_manager_tags = var.node_pool_auto_config.resource_manager_tags + node_kubelet_config { + insecure_kubelet_readonly_port_enabled = upper(var.node_pool_auto_config.kubelet_readonly_port_enabled) + } + linux_node_config { + cgroup_mode = var.node_pool_auto_config.cgroup_mode + } + } addons_config { cloudrun_config { disabled = !var.enable_addons.cloudrun diff --git a/modules/gke-cluster-standard/variables.tf b/modules/gke-cluster-standard/variables.tf index 6ced01a27..4d33815f2 100644 --- a/modules/gke-cluster-standard/variables.tf +++ b/modules/gke-cluster-standard/variables.tf @@ -415,6 +415,23 @@ variable "node_locations" { nullable = false } +variable "node_pool_auto_config" { + description = "Node pool configs that apply to auto-provisioned node pools in autopilot clusters and node auto-provisioning-enabled clusters." + type = object({ + cgroup_mode = optional(string) + kubelet_readonly_port_enabled = optional(bool, true) + network_tags = optional(list(string), []) + resource_manager_tags = optional(map(string), {}) + }) + default = {} + nullable = false + validation { + condition = contains(["CGROUPMODE_UNSPECIFIED", "CGROUPMODE_V1", "CGROUPMODE_V2"], + coalesce(var.node_pool_auto_config.cgroup_mode, "CGROUPMODE_UNSPECIFIED")) + error_message = "node_pool_auto_config.cgroup_mode must be CGROUPMODE_UNSPECIFIED, CGROUPMODE_V1 or CGROUPMODE_V2" + } +} + variable "project_id" { description = "Cluster project id." type = string diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 2968c54db..61ab2bd46 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -752,7 +752,7 @@ module "vpc" { } ] } -# tftest modules=1 resources=6 inventory=ipv6_only.yaml e2e +# tftest modules=1 resources=6 inventory=ipv6_only.yaml ``` ## Variables diff --git a/modules/net-vpc/subnets.tf b/modules/net-vpc/subnets.tf index b66721cef..3eef14384 100644 --- a/modules/net-vpc/subnets.tf +++ b/modules/net-vpc/subnets.tf @@ -150,6 +150,7 @@ resource "google_compute_subnetwork" "subnetwork" { ip_cidr_range = try(each.value.ipv6.ipv6_only, false) ? null : each.value.ip_cidr_range allow_subnet_cidr_routes_overlap = each.value.allow_subnet_cidr_routes_overlap description = ( + # Set description to an empty string (eg "") to create subnet without a description. each.value.description == null ? "Terraform-managed." : each.value.description @@ -201,9 +202,11 @@ resource "google_compute_subnetwork" "proxy_only" { name = each.value.name region = each.value.region ip_cidr_range = each.value.ip_cidr_range - description = coalesce( - each.value.description, - "Terraform-managed proxy-only subnet for Regional HTTPS, Internal HTTPS or Cross-Regional HTTPS Internal LB." + description = ( + # Set description to an empty string (eg "") to create subnet without a description. + each.value.description == null + ? "Terraform-managed proxy-only subnet for Regional HTTPS, Internal HTTPS or Cross-Regional HTTPS Internal LB." + : each.value.description ) purpose = each.value.global ? "GLOBAL_MANAGED_PROXY" : "REGIONAL_MANAGED_PROXY" role = each.value.active ? "ACTIVE" : "BACKUP" @@ -216,9 +219,11 @@ resource "google_compute_subnetwork" "private_nat" { name = each.value.name region = each.value.region ip_cidr_range = each.value.ip_cidr_range - description = coalesce( - each.value.description, - "Terraform-managed private NAT subnet." + description = ( + # Set description to an empty string (eg "") to create subnet without a description. + each.value.description == null + ? "Terraform-managed private NAT subnet." + : each.value.description ) purpose = "PRIVATE_NAT" } @@ -230,9 +235,11 @@ resource "google_compute_subnetwork" "psc" { name = each.value.name region = each.value.region ip_cidr_range = each.value.ip_cidr_range - description = coalesce( - each.value.description, - "Terraform-managed subnet for Private Service Connect (PSC NAT)." + description = ( + # Set description to an empty string (eg "") to create subnet without a description. + each.value.description == null + ? "Terraform-managed subnet for Private Service Connect (PSC NAT)." + : each.value.description ) purpose = "PRIVATE_SERVICE_CONNECT" } diff --git a/modules/project-factory/automation.tf b/modules/project-factory/automation.tf index 3c4b96152..e03d850ac 100644 --- a/modules/project-factory/automation.tf +++ b/modules/project-factory/automation.tf @@ -53,6 +53,7 @@ module "automation-bucket" { prefix = each.value.prefix name = "tf-state" encryption_key = lookup(each.value, "encryption_key", null) + force_destroy = lookup(each.value, "force_destroy", null) iam = { for k, v in lookup(each.value, "iam", {}) : k => [ for vv in v : try( diff --git a/modules/project-factory/factory-projects.tf b/modules/project-factory/factory-projects.tf index eb0996f39..0df5b260a 100644 --- a/modules/project-factory/factory-projects.tf +++ b/modules/project-factory/factory-projects.tf @@ -73,6 +73,7 @@ locals { name = name description = lookup(opts, "description", "Terraform-managed.") encryption_key = lookup(opts, "encryption_key", null) + force_destroy = lookup(opts, "force_destroy", null) iam = lookup(opts, "iam", {}) iam_bindings = lookup(opts, "iam_bindings", {}) iam_bindings_additive = lookup(opts, "iam_bindings_additive", {}) diff --git a/modules/project-factory/main.tf b/modules/project-factory/main.tf index 71022959e..f9e2e9ef7 100644 --- a/modules/project-factory/main.tf +++ b/modules/project-factory/main.tf @@ -325,6 +325,7 @@ module "buckets" { prefix = each.value.prefix name = "${each.value.project_name}-${each.value.name}" encryption_key = each.value.encryption_key + force_destroy = each.value.force_destroy iam = { for k, v in each.value.iam : k => [ for vv in v : try( diff --git a/modules/project-factory/schemas/project.schema.json b/modules/project-factory/schemas/project.schema.json index e2c4620c3..15ab12e98 100644 --- a/modules/project-factory/schemas/project.schema.json +++ b/modules/project-factory/schemas/project.schema.json @@ -459,6 +459,9 @@ "iam_bindings_additive": { "$ref": "#/$defs/iam_bindings_additive" }, + "force_destroy": { + "type": "boolean" + }, "labels": { "type": "object", "additionalProperties": { diff --git a/modules/project-factory/schemas/project.schema.md b/modules/project-factory/schemas/project.schema.md index cdd081ef4..0be198251 100644 --- a/modules/project-factory/schemas/project.schema.md +++ b/modules/project-factory/schemas/project.schema.md @@ -136,6 +136,7 @@ - **iam**: *reference([iam](#refs-iam))* - **iam_bindings**: *reference([iam_bindings](#refs-iam_bindings))* - **iam_bindings_additive**: *reference([iam_bindings_additive](#refs-iam_bindings_additive))* + - **force_destroy**: *boolean* - **labels**: *object* *additional properties: String* - **location**: *string* diff --git a/tests/examples_e2e/setup_module/main.tf b/tests/examples_e2e/setup_module/main.tf index d5d6470b4..1551c5af7 100644 --- a/tests/examples_e2e/setup_module/main.tf +++ b/tests/examples_e2e/setup_module/main.tf @@ -83,11 +83,12 @@ resource "google_project_service" "project_service" { } resource "google_storage_bucket" "bucket" { - location = var.region - name = "${local.prefix}-bucket" - project = google_project.project.project_id - force_destroy = true - depends_on = [google_project_service.project_service] + location = var.region + name = "${local.prefix}-bucket" + project = google_project.project.project_id + force_destroy = true + uniform_bucket_level_access = true + depends_on = [google_project_service.project_service] } resource "google_compute_network" "network" {