diff --git a/modules/cloud-run-v2/README.md b/modules/cloud-run-v2/README.md index 330fd0258..72c79fdfa 100644 --- a/modules/cloud-run-v2/README.md +++ b/modules/cloud-run-v2/README.md @@ -21,6 +21,7 @@ Cloud Run Services and Jobs, with support for IAM roles and Eventarc trigger cre - [Tag bindings](#tag-bindings) - [IAP Configuration](#iap-configuration) - [Adding GPUs](#adding-gpus) +- [Multi-Region Service](#multi-region-service) - [Variables](#variables) - [Outputs](#outputs) - [Fixtures](#fixtures) @@ -976,6 +977,30 @@ module "worker" { } # tftest inventory=gpu-workerpool.yaml e2e ``` + +## Multi-Region Service + +```hcl +module "cloud_run" { + source = "./fabric/modules/cloud-run-v2" + project_id = var.project_id + region = "global" + name = "example-mr" + containers = { + hello = { + image = "us-docker.pkg.dev/cloudrun/container/hello" + } + } + service_config = { + ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER" + multi_region_settings = { + regions = ["europe-west8", "europe-west1"] + } + } + deletion_protection = false +} +# tftest inventory=multiregion.yaml +``` ## Variables @@ -996,11 +1021,11 @@ module "worker" { | [revision](variables.tf#L197) | Revision template configurations. | object({…}) | | {} | | [service_account_config](variables-serviceaccount.tf#L17) | Service account configurations. | object({…}) | | {} | | [service_config](variables.tf#L264) | Cloud Run service specific configuration options. | object({…}) | | {} | -| [tag_bindings](variables.tf#L327) | Tag bindings for this service, in key => tag value id format. | map(string) | | {} | -| [type](variables.tf#L334) | Type of Cloud Run resource to deploy: JOB, SERVICE or WORKERPOOL. | string | | "SERVICE" | -| [volumes](variables.tf#L344) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | +| [tag_bindings](variables.tf#L330) | Tag bindings for this service, in key => tag value id format. | map(string) | | {} | +| [type](variables.tf#L337) | Type of Cloud Run resource to deploy: JOB, SERVICE or WORKERPOOL. | string | | "SERVICE" | +| [volumes](variables.tf#L347) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | | [vpc_connector_create](variables-vpcconnector.tf#L17) | VPC connector network configuration. Must be provided if new VPC connector is being created. | object({…}) | | null | -| [workerpool_config](variables.tf#L378) | Cloud Run Worker Pool specific configuration. | object({…}) | | {} | +| [workerpool_config](variables.tf#L381) | Cloud Run Worker Pool specific configuration. | object({…}) | | {} | ## Outputs diff --git a/modules/cloud-run-v2/main.tf b/modules/cloud-run-v2/main.tf index 82c3dae66..4333ca5a2 100644 --- a/modules/cloud-run-v2/main.tf +++ b/modules/cloud-run-v2/main.tf @@ -45,6 +45,14 @@ locals { location = lookup(local.ctx.locations, var.region, var.region) project_id = lookup(local.ctx.project_ids, var.project_id, var.project_id) + multi_region_regions = ( + try(var.service_config.multi_region_settings.regions, null) == null + ? null + : [ + for r in var.service_config.multi_region_settings.regions : + lookup(local.ctx.locations, r, r) + ] + ) revision_name = ( var.revision.name == null ? null : "${var.name}-${var.revision.name}" diff --git a/modules/cloud-run-v2/service-managed.tf b/modules/cloud-run-v2/service-managed.tf index a6078c13d..06a7a7c1a 100644 --- a/modules/cloud-run-v2/service-managed.tf +++ b/modules/cloud-run-v2/service-managed.tf @@ -28,6 +28,13 @@ resource "google_cloud_run_v2_service" "service" { deletion_protection = var.deletion_protection iap_enabled = var.service_config.iap_config != null + dynamic "multi_region_settings" { + for_each = local.multi_region_regions == null ? [] : [""] + content { + regions = local.multi_region_regions + } + } + template { labels = var.revision.labels encryption_key = var.encryption_key diff --git a/modules/cloud-run-v2/service-unmanaged.tf b/modules/cloud-run-v2/service-unmanaged.tf index 033004330..800e013f4 100644 --- a/modules/cloud-run-v2/service-unmanaged.tf +++ b/modules/cloud-run-v2/service-unmanaged.tf @@ -28,6 +28,13 @@ resource "google_cloud_run_v2_service" "service_unmanaged" { deletion_protection = var.deletion_protection iap_enabled = var.service_config.iap_config != null + dynamic "multi_region_settings" { + for_each = local.multi_region_regions == null ? [] : [""] + content { + regions = local.multi_region_regions + } + } + template { labels = var.revision.labels encryption_key = var.encryption_key diff --git a/modules/cloud-run-v2/variables.tf b/modules/cloud-run-v2/variables.tf index 5d74fc09f..01e25e514 100644 --- a/modules/cloud-run-v2/variables.tf +++ b/modules/cloud-run-v2/variables.tf @@ -286,6 +286,9 @@ variable "service_config" { ingress = optional(string, null) invoker_iam_disabled = optional(bool, false) max_concurrency = optional(number) + multi_region_settings = optional(object({ + regions = list(string) + }), null) scaling = optional(object({ max_instance_count = optional(number) min_instance_count = optional(number) diff --git a/tests/modules/cloud_run_v2/context.tfvars b/tests/modules/cloud_run_v2/context.tfvars index bf19aeb91..7a97d4161 100644 --- a/tests/modules/cloud_run_v2/context.tfvars +++ b/tests/modules/cloud_run_v2/context.tfvars @@ -67,3 +67,9 @@ tag_bindings = { baz = "$tag_values:test/one" foo = "$${projects[\"test-00\"].test}/cc-123" } +service_config = { + multi_region_settings = { + regions = ["$locations:ew8", "europe-west1"] + } +} + diff --git a/tests/modules/cloud_run_v2/context.yaml b/tests/modules/cloud_run_v2/context.yaml index bdfbec77d..1f112329d 100644 --- a/tests/modules/cloud_run_v2/context.yaml +++ b/tests/modules/cloud_run_v2/context.yaml @@ -1,10 +1,10 @@ -# Copyright 2025 Google LLC +# Copyright 2026 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 +# 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, @@ -21,6 +21,7 @@ values: client_version: null custom_audiences: null default_uri_disabled: null + deletion_policy: DELETE deletion_protection: true description: null effective_labels: @@ -29,7 +30,10 @@ values: invoker_iam_disabled: false labels: null location: europe-west8 - multi_region_settings: [] + multi_region_settings: + - regions: + - europe-west8 + - europe-west1 name: test-run-context project: foo-test-0 template: @@ -38,6 +42,7 @@ values: encryption_key: null execution_environment: EXECUTION_ENVIRONMENT_GEN1 gpu_zonal_redundancy_disabled: null + health_check_disabled: null labels: null node_selector: [] revision: null @@ -61,6 +66,7 @@ values: google_service_account.service_account[0]: account_id: test-run-context create_ignore_already_exists: null + deletion_policy: DELETE description: null disabled: false display_name: test-run-context @@ -68,7 +74,23 @@ values: member: serviceAccount:test-run-context@foo-test-0.iam.gserviceaccount.com project: foo-test-0 timeouts: null + google_tags_location_tag_binding.binding["bar"]: + deletion_policy: DELETE + location: europe-west8 + tag_value: tagValues/1234567891 + timeouts: null + google_tags_location_tag_binding.binding["baz"]: + deletion_policy: DELETE + location: europe-west8 + tag_value: tagValues/1234567890 + timeouts: null + google_tags_location_tag_binding.binding["foo"]: + deletion_policy: DELETE + location: europe-west8 + tag_value: foo-test-0/dynamic_test/cc-123 + timeouts: null google_vpc_access_connector.connector[0]: + deletion_policy: DELETE ip_cidr_range: 10.10.20.0/28 machine_type: e2-micro max_instances: 10 @@ -79,21 +101,27 @@ values: region: europe-west8 subnet: [] timeouts: null - google_tags_location_tag_binding.binding["bar"]: - location: europe-west8 - tag_value: tagValues/1234567891 - timeouts: null - google_tags_location_tag_binding.binding["baz"]: - location: europe-west8 - tag_value: tagValues/1234567890 - timeouts: null - google_tags_location_tag_binding.binding["foo"]: - location: europe-west8 - tag_value: foo-test-0/dynamic_test/cc-123 - timeouts: null counts: google_cloud_run_v2_service: 1 google_cloud_run_v2_service_iam_binding: 1 + google_project_iam_member: 1 + google_service_account: 1 google_tags_location_tag_binding: 3 google_vpc_access_connector: 1 + modules: 0 + resources: 8 + +outputs: + id: __missing__ + invoke_command: __missing__ + job: null + resource: __missing__ + resource_name: __missing__ + service: __missing__ + service_account: __missing__ + service_account_email: test-run-context@foo-test-0.iam.gserviceaccount.com + service_account_iam_email: serviceAccount:test-run-context@foo-test-0.iam.gserviceaccount.com + service_name: __missing__ + service_uri: __missing__ + vpc_connector: __missing__ diff --git a/tests/modules/cloud_run_v2/examples/multiregion.yaml b/tests/modules/cloud_run_v2/examples/multiregion.yaml new file mode 100644 index 000000000..1a109699e --- /dev/null +++ b/tests/modules/cloud_run_v2/examples/multiregion.yaml @@ -0,0 +1,99 @@ +# Copyright 2026 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. + +values: + module.cloud_run.google_cloud_run_v2_service.service[0]: + annotations: null + binary_authorization: [] + build_config: [] + client: null + client_version: null + custom_audiences: null + default_uri_disabled: null + deletion_policy: DELETE + deletion_protection: false + description: null + effective_labels: + goog-terraform-provisioned: 'true' + iap_enabled: false + ingress: INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER + invoker_iam_disabled: false + labels: null + location: global + multi_region_settings: + - regions: + - europe-west8 + - europe-west1 + name: example-mr + project: project-id + template: + - annotations: null + containers: + - args: null + base_image_uri: null + command: null + depends_on: null + env: [] + image: us-docker.pkg.dev/cloudrun/container/hello + liveness_probe: [] + name: hello + readiness_probe: [] + source_code: [] + volume_mounts: [] + working_dir: null + encryption_key: null + execution_environment: EXECUTION_ENVIRONMENT_GEN1 + gpu_zonal_redundancy_disabled: null + health_check_disabled: null + labels: null + node_selector: [] + revision: null + service_account: example-mr@project-id.iam.gserviceaccount.com + service_mesh: [] + session_affinity: null + volumes: [] + vpc_access: [] + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + module.cloud_run.google_project_iam_member.default["roles/logging.logWriter"]: + condition: [] + member: serviceAccount:example-mr@project-id.iam.gserviceaccount.com + project: project-id + role: roles/logging.logWriter + module.cloud_run.google_project_iam_member.default["roles/monitoring.metricWriter"]: + condition: [] + member: serviceAccount:example-mr@project-id.iam.gserviceaccount.com + project: project-id + role: roles/monitoring.metricWriter + module.cloud_run.google_service_account.service_account[0]: + account_id: example-mr + create_ignore_already_exists: null + deletion_policy: DELETE + description: null + disabled: false + display_name: example-mr + email: example-mr@project-id.iam.gserviceaccount.com + member: serviceAccount:example-mr@project-id.iam.gserviceaccount.com + project: project-id + timeouts: null + +counts: + google_cloud_run_v2_service: 1 + google_project_iam_member: 2 + google_service_account: 1 + modules: 1 + resources: 4 + +outputs: {} diff --git a/tests/modules/cloud_run_v2/multiregion.tfvars b/tests/modules/cloud_run_v2/multiregion.tfvars new file mode 100644 index 000000000..f6ae25a66 --- /dev/null +++ b/tests/modules/cloud_run_v2/multiregion.tfvars @@ -0,0 +1,15 @@ +name = "test-run-multiregion" +project_id = "test-project" +region = "global" + +containers = { + first = { + image = "gcr.io/cloudrun/hello" + } +} + +service_config = { + multi_region_settings = { + regions = ["europe-west8", "europe-west1"] + } +} diff --git a/tests/modules/cloud_run_v2/multiregion.yaml b/tests/modules/cloud_run_v2/multiregion.yaml new file mode 100644 index 000000000..11d3a1abe --- /dev/null +++ b/tests/modules/cloud_run_v2/multiregion.yaml @@ -0,0 +1,110 @@ +# Copyright 2026 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. + +values: + google_cloud_run_v2_service.service[0]: + annotations: null + binary_authorization: [] + build_config: [] + client: null + client_version: null + custom_audiences: null + default_uri_disabled: null + deletion_policy: DELETE + deletion_protection: true + description: null + effective_labels: + goog-terraform-provisioned: 'true' + iap_enabled: false + invoker_iam_disabled: false + labels: null + location: global + multi_region_settings: + - regions: + - europe-west8 + - europe-west1 + name: test-run-multiregion + project: test-project + template: + - annotations: null + containers: + - args: null + base_image_uri: null + command: null + depends_on: null + env: [] + image: gcr.io/cloudrun/hello + liveness_probe: [] + name: first + readiness_probe: [] + source_code: [] + volume_mounts: [] + working_dir: null + encryption_key: null + execution_environment: EXECUTION_ENVIRONMENT_GEN1 + gpu_zonal_redundancy_disabled: null + health_check_disabled: null + labels: null + node_selector: [] + revision: null + service_account: test-run-multiregion@test-project.iam.gserviceaccount.com + service_mesh: [] + session_affinity: null + volumes: [] + vpc_access: [] + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + google_project_iam_member.default["roles/logging.logWriter"]: + condition: [] + member: serviceAccount:test-run-multiregion@test-project.iam.gserviceaccount.com + project: test-project + role: roles/logging.logWriter + google_project_iam_member.default["roles/monitoring.metricWriter"]: + condition: [] + member: serviceAccount:test-run-multiregion@test-project.iam.gserviceaccount.com + project: test-project + role: roles/monitoring.metricWriter + google_service_account.service_account[0]: + account_id: test-run-multiregion + create_ignore_already_exists: null + deletion_policy: DELETE + description: null + disabled: false + display_name: test-run-multiregion + email: test-run-multiregion@test-project.iam.gserviceaccount.com + member: serviceAccount:test-run-multiregion@test-project.iam.gserviceaccount.com + project: test-project + timeouts: null + +counts: + google_cloud_run_v2_service: 1 + google_project_iam_member: 2 + google_service_account: 1 + modules: 0 + resources: 4 + +outputs: + id: __missing__ + invoke_command: __missing__ + job: null + resource: __missing__ + resource_name: __missing__ + service: __missing__ + service_account: __missing__ + service_account_email: test-run-multiregion@test-project.iam.gserviceaccount.com + service_account_iam_email: serviceAccount:test-run-multiregion@test-project.iam.gserviceaccount.com + service_name: __missing__ + service_uri: __missing__ + vpc_connector: null diff --git a/tests/modules/cloud_run_v2/tftest.yaml b/tests/modules/cloud_run_v2/tftest.yaml index cf724f5f0..c413d43f1 100644 --- a/tests/modules/cloud_run_v2/tftest.yaml +++ b/tests/modules/cloud_run_v2/tftest.yaml @@ -18,3 +18,5 @@ tests: context-subnet: context-subnet-project: vpcconnector: + multiregion: +