From 2e42c1b548c676db3012ab8e6c5965ff81ec6413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Sun, 26 Oct 2025 14:33:26 +0000 Subject: [PATCH] service_account_config for Cloud Run v2 Additional changes: * align vpc-connector interface to Cloud Functions * split managed and unmanaged resources into separate files, this makes easier to introduce further changes * add support for contexts * move `vpc_connector` variable to variables.tf for Cloud Functions * remove `create` from `vpc_connector` in Cloud Functions as it was sharing the meaning with `vpc_connector_create` --- modules/cloud-function-v1/README.md | 4 +- modules/cloud-function-v1/main.tf | 2 +- .../variables-vpcconnector.tf | 13 +- modules/cloud-function-v1/variables.tf | 10 + modules/cloud-function-v2/README.md | 4 +- modules/cloud-function-v2/main.tf | 2 +- .../variables-vpcconnector.tf | 13 +- modules/cloud-function-v2/variables.tf | 10 + modules/cloud-run-v2/README.md | 106 ++-- modules/cloud-run-v2/job-managed.tf | 223 +++++++ modules/cloud-run-v2/job-unmanaged.tf | 227 +++++++ modules/cloud-run-v2/job.tf | 412 +----------- modules/cloud-run-v2/main.tf | 93 +-- .../recipes/cloudsql-iam-auth-proxy/README.md | 7 +- modules/cloud-run-v2/service-managed.tf | 271 ++++++++ modules/cloud-run-v2/service-unmanaged.tf | 274 ++++++++ modules/cloud-run-v2/service.tf | 584 +++--------------- modules/cloud-run-v2/serviceaccount.tf | 53 ++ modules/cloud-run-v2/tags.tf | 6 +- .../cloud-run-v2/variables-serviceaccount.tf | 31 + .../cloud-run-v2/variables-vpcconnector.tf | 10 +- modules/cloud-run-v2/variables.tf | 36 +- modules/cloud-run-v2/vpcconnector.tf | 47 +- modules/cloud-run-v2/workerpool-managed.tf | 190 ++++++ modules/cloud-run-v2/workerpool-unmanaged.tf | 193 ++++++ modules/cloud-run-v2/workerpool.tf | 344 +---------- .../recipe-cloud-run-iap/README.md | 2 +- .../recipe-cloud-run-iap/main.tf | 3 +- .../context-subnet-project.tfvars | 3 +- .../cloud_function_v1/context-subnet.tfvars | 3 +- .../modules/cloud_function_v1/context.tfvars | 3 +- .../context-subnet-project.tfvars | 3 +- .../cloud_function_v2/context-subnet.tfvars | 3 +- .../modules/cloud_function_v2/context.tfvars | 3 +- .../context-subnet-project.tfvars | 60 ++ .../cloud_run_v2/context-subnet-project.yaml | 88 +++ .../cloud_run_v2/context-subnet.tfvars | 58 ++ .../modules/cloud_run_v2/context-subnet.yaml | 78 +++ tests/modules/cloud_run_v2/context.tfvars | 54 ++ tests/modules/cloud_run_v2/context.yaml | 87 +++ .../cloud_run_v2/examples/cloudsql.yaml | 2 +- tests/modules/cloud_run_v2/examples/cmek.yaml | 90 +++ .../cloud_run_v2/examples/gcs-mount.yaml | 2 +- .../cloud_run_v2/examples/gpu-job.yaml | 4 +- .../cloud_run_v2/examples/gpu-service.yaml | 2 - .../cloud_run_v2/examples/gpu-workerpool.yaml | 2 - tests/modules/cloud_run_v2/examples/iap.yaml | 46 ++ .../cloud_run_v2/examples/job-iam-env.yaml | 4 +- .../examples/service-direct-vpc.yaml | 4 +- ...ervice-eventarc-auditlogs-external-sa.yaml | 10 +- .../examples/service-eventarc-pubsub.yaml | 8 +- .../examples/service-eventarc-storage.yaml | 8 +- .../examples/service-external-sa.yaml | 2 +- .../examples/service-iam-env.yaml | 6 +- .../examples/service-invoker-iam-disable.yaml | 4 +- .../examples/service-otel-sidecar.yaml | 2 - .../examples/service-sa-create.yaml | 89 ++- .../examples/service-volume-secretes.yaml | 5 +- ...vpc-access-connector-create-sharedvpc.yaml | 7 +- .../service-vpc-access-connector-create.yaml | 6 +- .../service-vpc-access-connector.yaml | 5 +- tests/modules/cloud_run_v2/examples/tags.yaml | 2 - tests/modules/cloud_run_v2/tftest.yaml | 20 + .../modules/cloud_run_v2/vpcconnector.tfvars | 9 + tests/modules/cloud_run_v2/vpcconnector.yaml | 71 +++ tools/duplicate-diff.py | 4 + 66 files changed, 2484 insertions(+), 1543 deletions(-) create mode 100644 modules/cloud-run-v2/job-managed.tf create mode 100644 modules/cloud-run-v2/job-unmanaged.tf create mode 100644 modules/cloud-run-v2/service-managed.tf create mode 100644 modules/cloud-run-v2/service-unmanaged.tf create mode 100644 modules/cloud-run-v2/serviceaccount.tf create mode 100644 modules/cloud-run-v2/variables-serviceaccount.tf create mode 100644 modules/cloud-run-v2/workerpool-managed.tf create mode 100644 modules/cloud-run-v2/workerpool-unmanaged.tf create mode 100644 tests/modules/cloud_run_v2/context-subnet-project.tfvars create mode 100644 tests/modules/cloud_run_v2/context-subnet-project.yaml create mode 100644 tests/modules/cloud_run_v2/context-subnet.tfvars create mode 100644 tests/modules/cloud_run_v2/context-subnet.yaml create mode 100644 tests/modules/cloud_run_v2/context.tfvars create mode 100644 tests/modules/cloud_run_v2/context.yaml create mode 100644 tests/modules/cloud_run_v2/examples/cmek.yaml create mode 100644 tests/modules/cloud_run_v2/examples/iap.yaml create mode 100644 tests/modules/cloud_run_v2/tftest.yaml create mode 100644 tests/modules/cloud_run_v2/vpcconnector.tfvars create mode 100644 tests/modules/cloud_run_v2/vpcconnector.yaml diff --git a/modules/cloud-function-v1/README.md b/modules/cloud-function-v1/README.md index 158522750..d0897592f 100644 --- a/modules/cloud-function-v1/README.md +++ b/modules/cloud-function-v1/README.md @@ -500,8 +500,8 @@ module "cf_http" { | [secrets](variables.tf#L194) | Secret Manager secrets. Key is the variable name or mountpoint, volume versions are in version:path format. | map(object({…})) | | {} | | [service_account_config](variables-serviceaccount.tf#L17) | Service account configurations. | object({…}) | | {} | | [trigger_config](variables.tf#L206) | Function trigger configuration. Leave null for HTTP trigger. | object({…}) | | null | -| [vpc_connector](variables-vpcconnector.tf#L17) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | object({…}) | | {} | -| [vpc_connector_create](variables-vpcconnector.tf#L28) | VPC connector network configuration. Must be provided if new VPC connector is being created. | object({…}) | | null | +| [vpc_connector](variables.tf#L216) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | object({…}) | | {} | +| [vpc_connector_create](variables-vpcconnector.tf#L17) | VPC connector network configuration. Must be provided if new VPC connector is being created. | object({…}) | | null | ## Outputs diff --git a/modules/cloud-function-v1/main.tf b/modules/cloud-function-v1/main.tf index 4c6cc0f07..4df938b00 100644 --- a/modules/cloud-function-v1/main.tf +++ b/modules/cloud-function-v1/main.tf @@ -33,7 +33,7 @@ locals { location = lookup(local.ctx.locations, var.region, var.region) prefix = var.prefix == null ? "" : "${var.prefix}-" project_id = lookup(local.ctx.project_ids, var.project_id, var.project_id) - vpc_connector = var.vpc_connector.create ? google_vpc_access_connector.connector[0].id : var.vpc_connector.name + vpc_connector = var.vpc_connector_create != null ? google_vpc_access_connector.connector[0].id : var.vpc_connector.name } resource "google_cloudfunctions_function" "function" { diff --git a/modules/cloud-function-v1/variables-vpcconnector.tf b/modules/cloud-function-v1/variables-vpcconnector.tf index 967ce6983..e45ebe20e 100644 --- a/modules/cloud-function-v1/variables-vpcconnector.tf +++ b/modules/cloud-function-v1/variables-vpcconnector.tf @@ -14,17 +14,6 @@ * limitations under the License. */ -variable "vpc_connector" { - description = "VPC connector configuration. Set create to 'true' if a new connector needs to be created." - type = object({ - create = optional(bool, false) - name = optional(string) - egress_settings = optional(string) - }) - nullable = false - default = {} -} - variable "vpc_connector_create" { description = "VPC connector network configuration. Must be provided if new VPC connector is being created." type = object({ @@ -50,7 +39,7 @@ variable "vpc_connector_create" { default = null validation { condition = ( - var.vpc_connector.create == false || + var.vpc_connector_create == null || try(var.vpc_connector_create.instances, null) != null || try(var.vpc_connector_create.throughput, null) != null ) diff --git a/modules/cloud-function-v1/variables.tf b/modules/cloud-function-v1/variables.tf index c089bbb07..8d0a1cc4c 100644 --- a/modules/cloud-function-v1/variables.tf +++ b/modules/cloud-function-v1/variables.tf @@ -212,3 +212,13 @@ variable "trigger_config" { }) default = null } + +variable "vpc_connector" { + description = "VPC connector configuration. Set create to 'true' if a new connector needs to be created." + type = object({ + name = optional(string) + egress_settings = optional(string) + }) + nullable = false + default = {} +} diff --git a/modules/cloud-function-v2/README.md b/modules/cloud-function-v2/README.md index 9f60b645f..c4ae121d3 100644 --- a/modules/cloud-function-v2/README.md +++ b/modules/cloud-function-v2/README.md @@ -432,8 +432,8 @@ module "cf_http" { | [secrets](variables.tf#L192) | Secret Manager secrets. Key is the variable name or mountpoint, volume versions are in version:path format. | map(object({…})) | | {} | | [service_account_config](variables-serviceaccount.tf#L17) | Service account configurations. | object({…}) | | {} | | [trigger_config](variables.tf#L204) | Function trigger configuration. Leave null for HTTP trigger. | object({…}) | | null | -| [vpc_connector](variables-vpcconnector.tf#L17) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | object({…}) | | {} | -| [vpc_connector_create](variables-vpcconnector.tf#L28) | VPC connector network configuration. Must be provided if new VPC connector is being created. | object({…}) | | null | +| [vpc_connector](variables.tf#L222) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | object({…}) | | {} | +| [vpc_connector_create](variables-vpcconnector.tf#L17) | VPC connector network configuration. Must be provided if new VPC connector is being created. | object({…}) | | null | ## Outputs diff --git a/modules/cloud-function-v2/main.tf b/modules/cloud-function-v2/main.tf index 7905158ca..e1273aecc 100644 --- a/modules/cloud-function-v2/main.tf +++ b/modules/cloud-function-v2/main.tf @@ -41,7 +41,7 @@ locals { var.trigger_config.service_account_email, null ) - vpc_connector = var.vpc_connector.create ? google_vpc_access_connector.connector[0].id : var.vpc_connector.name + vpc_connector = var.vpc_connector_create != null ? google_vpc_access_connector.connector[0].id : var.vpc_connector.name } resource "google_cloudfunctions2_function" "function" { diff --git a/modules/cloud-function-v2/variables-vpcconnector.tf b/modules/cloud-function-v2/variables-vpcconnector.tf index 967ce6983..e45ebe20e 100644 --- a/modules/cloud-function-v2/variables-vpcconnector.tf +++ b/modules/cloud-function-v2/variables-vpcconnector.tf @@ -14,17 +14,6 @@ * limitations under the License. */ -variable "vpc_connector" { - description = "VPC connector configuration. Set create to 'true' if a new connector needs to be created." - type = object({ - create = optional(bool, false) - name = optional(string) - egress_settings = optional(string) - }) - nullable = false - default = {} -} - variable "vpc_connector_create" { description = "VPC connector network configuration. Must be provided if new VPC connector is being created." type = object({ @@ -50,7 +39,7 @@ variable "vpc_connector_create" { default = null validation { condition = ( - var.vpc_connector.create == false || + var.vpc_connector_create == null || try(var.vpc_connector_create.instances, null) != null || try(var.vpc_connector_create.throughput, null) != null ) diff --git a/modules/cloud-function-v2/variables.tf b/modules/cloud-function-v2/variables.tf index 0926f8fd5..d8a115e23 100644 --- a/modules/cloud-function-v2/variables.tf +++ b/modules/cloud-function-v2/variables.tf @@ -218,3 +218,13 @@ variable "trigger_config" { }) default = null } + +variable "vpc_connector" { + description = "VPC connector configuration. Set create to 'true' if a new connector needs to be created." + type = object({ + name = optional(string) + egress_settings = optional(string) + }) + nullable = false + default = {} +} diff --git a/modules/cloud-run-v2/README.md b/modules/cloud-run-v2/README.md index f33a3a13f..8b802edad 100644 --- a/modules/cloud-run-v2/README.md +++ b/modules/cloud-run-v2/README.md @@ -34,7 +34,7 @@ IAM bindings support the usual syntax. Container environment values can be decla module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id - name = "hello" + name = "example-hello" region = var.region containers = { hello = { @@ -65,7 +65,7 @@ module "cloud_run" { module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id - name = "hello" + name = "example-hello" region = var.region containers = { hello = { @@ -95,7 +95,7 @@ module "cloud_run" { module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id - name = "hello" + name = "example-hello" region = var.region containers = { hello = { @@ -132,7 +132,7 @@ module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id region = var.region - name = "hello" + name = "example-hello" containers = { hello = { image = "us-docker.pkg.dev/cloudrun/container/hello" @@ -157,7 +157,7 @@ module "cloud_run" { module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id - name = "hello" + name = "example-hello" region = var.region containers = { hello = { @@ -190,7 +190,7 @@ module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id region = var.regions.secondary - name = "hello" + name = "example-hello" containers = { hello = { image = "us-docker.pkg.dev/cloudrun/container/hello" @@ -204,7 +204,7 @@ module "cloud_run" { } deletion_protection = false } -# tftest modules=1 resources=2 fixtures=fixtures/vpc-connector.tf inventory=service-vpc-access-connector.yaml e2e +# tftest fixtures=fixtures/vpc-connector.tf inventory=service-vpc-access-connector.yaml e2e ``` If creation of the VPC Access Connector is required, use the `vpc_connector_create` variable which also supports optional attributes like number of instances, machine type, or throughput. The connector will be used automatically by Cloud Run Service and Job. Worker Pool does not support connector. @@ -214,7 +214,7 @@ module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id region = var.region - name = "hello" + name = "example-hello" containers = { hello = { image = "us-docker.pkg.dev/cloudrun/container/hello" @@ -240,7 +240,7 @@ module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = module.project-service.project_id region = var.region - name = "hello" + name = "example-hello" containers = { hello = { image = "us-docker.pkg.dev/cloudrun/container/hello" @@ -301,7 +301,7 @@ module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = module.project.project_id region = var.region - name = "hello" + name = "example-hello" encryption_key = module.kms.keys.key-regional.id containers = { hello = { @@ -310,7 +310,7 @@ module "cloud_run" { } deletion_protection = false } -# tftest modules=3 resources=11 e2e +# tftest inventory=cmek.yaml e2e ``` ## Deploying OpenTelemetry Collector sidecar @@ -509,7 +509,7 @@ module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id region = var.region - name = "hello" + name = "example-hello" containers = { hello = { image = "us-docker.pkg.dev/cloudrun/container/hello" @@ -568,7 +568,7 @@ module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id region = var.region - name = "hello" + name = "example-hello" containers = { hello = { image = "us-docker.pkg.dev/cloudrun/container/hello" @@ -595,7 +595,7 @@ module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id region = var.region - name = "hello" + name = "example-hello" containers = { hello = { image = "us-docker.pkg.dev/cloudrun/container/hello" @@ -636,7 +636,7 @@ module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id region = var.region - name = "hello" + name = "example-hello" containers = { hello = { image = "us-docker.pkg.dev/cloudrun/container/hello" @@ -684,7 +684,7 @@ module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id region = var.region - name = "hello" + name = "example-hello" containers = { hello = { image = "us-docker.pkg.dev/cloudrun/container/hello" @@ -700,42 +700,54 @@ module "cloud_run" { ## Cloud Run Service Account -To use a custom service account managed by the module, set `service_account_create` to `true` and leave `service_account` set to `null` (default). +The module by default creates a service account that is associated with the Cloud Run instance. It grants the service account `roles/logging.logWriter` and `roles/monitoring.metricWriter` roles. + +To assign non-default roles, pass them as `service_account_config.roles`. ```hcl module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id region = var.region - name = "hello" + name = "example-hello" containers = { hello = { image = "us-docker.pkg.dev/cloudrun/container/hello" } } - service_account_create = true - deletion_protection = false + service_account_config = { + roles = [ + "roles/logging.logWriter", + "roles/monitoring.metricWriter", + "roles/cloudsql.client", + "roles/cloudsql.instanceUser", + ] + } + deletion_protection = false } # tftest inventory=service-sa-create.yaml e2e ``` -To use an externally managed service account, use its email in `service_account` and leave `service_account_create` to `false` (default). +To use externally managed service account, pass its email in `service_account_config.email` and set `service_account_config.email` to `false`. ```hcl module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id region = var.region - name = "hello" + name = "example-hello" containers = { hello = { image = "us-docker.pkg.dev/cloudrun/container/hello" } } - service_account = module.iam-service-account.email + service_account_config = { + create = false + email = module.iam-service-account.email + } deletion_protection = false } -# tftest modules=2 resources=2 fixtures=fixtures/iam-service-account.tf inventory=service-external-sa.yaml e2e +# tftest fixtures=fixtures/iam-service-account.tf inventory=service-external-sa.yaml e2e ``` ## Creating Cloud Run Jobs @@ -762,7 +774,7 @@ Additional configuration can be passwed as `job_config`: module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id - name = "hello" + name = "example-hello" region = var.region type = "JOB" containers = { @@ -854,7 +866,7 @@ IAP is only supported for service. Refer to the [Configure IAP directly on cloud module "cloud_run" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id - name = "hello" + name = "example-hello" region = var.region launch_stage = "BETA" containers = { @@ -869,7 +881,7 @@ module "cloud_run" { } deletion_protection = false } -# tftest modules=1 resources=2 e2e +# tftest inventory=iap.yaml e2e ``` ## Adding GPUs @@ -880,7 +892,7 @@ GPU support is available for all types of Cloud Run resources: jobs, services an module "job" { source = "./fabric/modules/cloud-run-v2" project_id = var.project_id - name = "job" + name = "example-job" region = var.region launch_stage = "BETA" revision = { @@ -974,26 +986,26 @@ module "worker" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L160) | Name used for Cloud Run service. | string | ✓ | | -| [project_id](variables.tf#L165) | Project id used for all resources. | string | ✓ | | -| [region](variables.tf#L170) | Region used for all resources. | string | ✓ | | +| [name](variables.tf#L178) | Name used for Cloud Run service. | string | ✓ | | +| [project_id](variables.tf#L183) | Project id used for all resources. | string | ✓ | | +| [region](variables.tf#L188) | Region used for all resources. | string | ✓ | | | [containers](variables.tf#L17) | Containers in name => attributes format. | map(object({…})) | | {} | -| [deletion_protection](variables.tf#L97) | Deletion protection setting for this Cloud Run service. | string | | null | -| [encryption_key](variables.tf#L103) | The full resource name of the Cloud KMS CryptoKey. | string | | null | -| [iam](variables.tf#L109) | IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [job_config](variables.tf#L115) | Cloud Run Job specific configuration. | object({…}) | | {} | -| [labels](variables.tf#L130) | Resource labels. | map(string) | | {} | -| [launch_stage](variables.tf#L136) | The launch stage as defined by Google Cloud Platform Launch Stages. | string | | null | -| [managed_revision](variables.tf#L153) | Whether the Terraform module should control the deployment of revisions. | bool | | true | -| [revision](variables.tf#L175) | Revision template configurations. | object({…}) | | {} | -| [service_account](variables.tf#L236) | Service account email. Unused if service account is auto-created. | string | | null | -| [service_account_create](variables.tf#L242) | Auto-create service account. | bool | | false | -| [service_config](variables.tf#L248) | Cloud Run service specific configuration options. | object({…}) | | {} | -| [tag_bindings](variables.tf#L311) | Tag bindings for this service, in key => tag value id format. | map(string) | | {} | -| [type](variables.tf#L318) | Type of Cloud Run resource to deploy: JOB, SERVICE or WORKERPOOL. | string | | "SERVICE" | -| [volumes](variables.tf#L328) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | -| [vpc_connector_create](variables-vpcconnector.tf#L17) | Populate this to create a Serverless VPC Access connector. | object({…}) | | null | -| [workerpool_config](variables.tf#L362) | Cloud Run Worker Pool specific configuration. | object({…}) | | {} | +| [context](variables.tf#L97) | Context-specific interpolations. | object({…}) | | {} | +| [deletion_protection](variables.tf#L115) | Deletion protection setting for this Cloud Run service. | string | | null | +| [encryption_key](variables.tf#L121) | The full resource name of the Cloud KMS CryptoKey. | string | | null | +| [iam](variables.tf#L127) | IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [job_config](variables.tf#L133) | Cloud Run Job specific configuration. | object({…}) | | {} | +| [labels](variables.tf#L148) | Resource labels. | map(string) | | {} | +| [launch_stage](variables.tf#L154) | The launch stage as defined by Google Cloud Platform Launch Stages. | string | | null | +| [managed_revision](variables.tf#L171) | Whether the Terraform module should control the deployment of revisions. | bool | | true | +| [revision](variables.tf#L193) | Revision template configurations. | object({…}) | | {} | +| [service_account_config](variables-serviceaccount.tf#L17) | Service account configurations. | object({…}) | | {} | +| [service_config](variables.tf#L260) | Cloud Run service specific configuration options. | object({…}) | | {} | +| [tag_bindings](variables.tf#L323) | Tag bindings for this service, in key => tag value id format. | map(string) | | {} | +| [type](variables.tf#L330) | Type of Cloud Run resource to deploy: JOB, SERVICE or WORKERPOOL. | string | | "SERVICE" | +| [volumes](variables.tf#L340) | 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#L374) | Cloud Run Worker Pool specific configuration. | object({…}) | | {} | ## Outputs diff --git a/modules/cloud-run-v2/job-managed.tf b/modules/cloud-run-v2/job-managed.tf new file mode 100644 index 000000000..c65578712 --- /dev/null +++ b/modules/cloud-run-v2/job-managed.tf @@ -0,0 +1,223 @@ +/** + * 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_cloud_run_v2_job" "job" { + count = var.type == "JOB" && var.managed_revision ? 1 : 0 + provider = google-beta + project = local.project_id + location = local.location + name = var.name + labels = var.labels + launch_stage = var.launch_stage + deletion_protection = var.deletion_protection + template { + labels = var.revision.labels + task_count = var.job_config.task_count + template { + encryption_key = var.encryption_key + gpu_zonal_redundancy_disabled = var.revision.gpu_zonal_redundancy_disabled + dynamic "node_selector" { + for_each = var.revision.node_selector == null ? [] : [""] + content { + accelerator = var.revision.node_selector.accelerator + } + } + dynamic "vpc_access" { + for_each = local.connector == null ? [] : [""] + content { + connector = local.connector + egress = try(var.revision.vpc_access.egress, null) + } + } + dynamic "vpc_access" { + for_each = var.revision.vpc_access.subnet == null && var.revision.vpc_access.network == null ? [] : [""] + content { + egress = var.revision.vpc_access.egress + network_interfaces { + subnetwork = var.revision.vpc_access.subnet == null ? null : lookup( + local.ctx.subnets, var.revision.vpc_access.subnet, + var.revision.vpc_access.subnet + ) + network = var.revision.vpc_access.network == null ? null : lookup( + local.ctx.networks, var.revision.vpc_access.network, + var.revision.vpc_access.network + ) + tags = var.revision.vpc_access.tags + } + } + } + max_retries = var.job_config.max_retries + timeout = var.job_config.timeout + service_account = local.service_account_email + dynamic "containers" { + for_each = var.containers + content { + name = containers.key + image = containers.value.image + depends_on = containers.value.depends_on + command = containers.value.command + args = containers.value.args + dynamic "env" { + for_each = coalesce(containers.value.env, tomap({})) + content { + name = env.key + value = env.value + } + } + dynamic "env" { + for_each = coalesce(containers.value.env_from_key, tomap({})) + content { + name = env.key + value_source { + secret_key_ref { + secret = env.value.secret + version = env.value.version + } + } + } + } + dynamic "resources" { + for_each = containers.value.resources == null ? [] : [""] + content { + limits = containers.value.resources.limits + } + } + dynamic "ports" { + for_each = coalesce(containers.value.ports, tomap({})) + content { + container_port = ports.value.container_port + name = ports.value.name + } + } + dynamic "volume_mounts" { + for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k != "cloudsql" } + content { + name = volume_mounts.key + mount_path = volume_mounts.value + } + } + # CloudSQL is the last mount in the list returned by API + dynamic "volume_mounts" { + for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k == "cloudsql" } + content { + name = volume_mounts.key + mount_path = volume_mounts.value + } + } + dynamic "startup_probe" { + for_each = containers.value.startup_probe == null ? [] : [""] + content { + initial_delay_seconds = containers.value.startup_probe.initial_delay_seconds + timeout_seconds = containers.value.startup_probe.timeout_seconds + period_seconds = containers.value.startup_probe.period_seconds + failure_threshold = containers.value.startup_probe.failure_threshold + dynamic "http_get" { + for_each = containers.value.startup_probe.http_get == null ? [] : [""] + content { + path = containers.value.startup_probe.http_get.path + port = containers.value.startup_probe.http_get.port + dynamic "http_headers" { + for_each = coalesce(containers.value.startup_probe.http_get.http_headers, tomap({})) + content { + name = http_headers.key + value = http_headers.value + } + } + } + } + dynamic "tcp_socket" { + for_each = containers.value.startup_probe.tcp_socket == null ? [] : [""] + content { + port = containers.value.startup_probe.tcp_socket.port + } + } + dynamic "grpc" { + for_each = containers.value.startup_probe.grpc == null ? [] : [""] + content { + port = containers.value.startup_probe.grpc.port + service = containers.value.startup_probe.grpc.service + } + } + } + } + } + } + dynamic "volumes" { + for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances == null } + content { + name = volumes.key + dynamic "secret" { + for_each = volumes.value.secret == null ? [] : [""] + content { + secret = volumes.value.secret.name + default_mode = volumes.value.secret.default_mode + dynamic "items" { + for_each = volumes.value.secret.path == null ? [] : [""] + content { + path = volumes.value.secret.path + version = volumes.value.secret.version + mode = volumes.value.secret.mode + } + } + } + } + + dynamic "empty_dir" { + for_each = volumes.value.empty_dir_size == null ? [] : [""] + content { + medium = "MEMORY" + size_limit = volumes.value.empty_dir_size + } + } + dynamic "gcs" { + for_each = volumes.value.gcs == null ? [] : [""] + content { + bucket = volumes.value.gcs.bucket + read_only = volumes.value.gcs.is_read_only + } + } + dynamic "nfs" { + for_each = volumes.value.nfs == null ? [] : [""] + content { + server = volumes.value.nfs.server + path = volumes.value.nfs.path + read_only = volumes.value.nfs.is_read_only + } + } + } + } + # CloudSQL is the last volume in the list returned by API + dynamic "volumes" { + for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances != null } + content { + name = volumes.key + dynamic "cloud_sql_instance" { + for_each = length(coalesce(volumes.value.cloud_sql_instances, [])) == 0 ? [] : [""] + content { + instances = volumes.value.cloud_sql_instances + } + } + } + } + } + } + + lifecycle { + ignore_changes = [ + template[0].annotations["run.googleapis.com/operation-id"], + ] + } +} diff --git a/modules/cloud-run-v2/job-unmanaged.tf b/modules/cloud-run-v2/job-unmanaged.tf new file mode 100644 index 000000000..1961acad6 --- /dev/null +++ b/modules/cloud-run-v2/job-unmanaged.tf @@ -0,0 +1,227 @@ +/** + * 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_cloud_run_v2_job" "job_unmanaged" { + count = var.type == "JOB" && !var.managed_revision ? 1 : 0 + provider = google-beta + project = local.project_id + location = local.location + name = var.name + labels = var.labels + launch_stage = var.launch_stage + deletion_protection = var.deletion_protection + template { + labels = var.revision.labels + task_count = var.job_config.task_count + template { + encryption_key = var.encryption_key + gpu_zonal_redundancy_disabled = var.revision.gpu_zonal_redundancy_disabled + dynamic "node_selector" { + for_each = var.revision.node_selector == null ? [] : [""] + content { + accelerator = var.revision.node_selector.accelerator + } + } + dynamic "vpc_access" { + for_each = local.connector == null ? [] : [""] + content { + connector = local.connector + egress = try(var.revision.vpc_access.egress, null) + } + } + dynamic "vpc_access" { + for_each = var.revision.vpc_access.subnet == null && var.revision.vpc_access.network == null ? [] : [""] + content { + egress = var.revision.vpc_access.egress + network_interfaces { + subnetwork = var.revision.vpc_access.subnet == null ? null : lookup( + local.ctx.subnets, var.revision.vpc_access.subnet, + var.revision.vpc_access.subnet + ) + network = var.revision.vpc_access.network == null ? null : lookup( + local.ctx.networks, var.revision.vpc_access.network, + var.revision.vpc_access.network + ) + tags = var.revision.vpc_access.tags + } + } + } + max_retries = var.job_config.max_retries + timeout = var.job_config.timeout + service_account = local.service_account_email + dynamic "containers" { + for_each = var.containers + content { + name = containers.key + image = containers.value.image + depends_on = containers.value.depends_on + command = containers.value.command + args = containers.value.args + dynamic "env" { + for_each = coalesce(containers.value.env, tomap({})) + content { + name = env.key + value = env.value + } + } + dynamic "env" { + for_each = coalesce(containers.value.env_from_key, tomap({})) + content { + name = env.key + value_source { + secret_key_ref { + secret = env.value.secret + version = env.value.version + } + } + } + } + dynamic "resources" { + for_each = containers.value.resources == null ? [] : [""] + content { + limits = containers.value.resources.limits + } + } + dynamic "ports" { + for_each = coalesce(containers.value.ports, tomap({})) + content { + container_port = ports.value.container_port + name = ports.value.name + } + } + dynamic "volume_mounts" { + for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k != "cloudsql" } + content { + name = volume_mounts.key + mount_path = volume_mounts.value + } + } + # CloudSQL is the last mount in the list returned by API + dynamic "volume_mounts" { + for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k == "cloudsql" } + content { + name = volume_mounts.key + mount_path = volume_mounts.value + } + } + dynamic "startup_probe" { + for_each = containers.value.startup_probe == null ? [] : [""] + content { + initial_delay_seconds = containers.value.startup_probe.initial_delay_seconds + timeout_seconds = containers.value.startup_probe.timeout_seconds + period_seconds = containers.value.startup_probe.period_seconds + failure_threshold = containers.value.startup_probe.failure_threshold + dynamic "http_get" { + for_each = containers.value.startup_probe.http_get == null ? [] : [""] + content { + path = containers.value.startup_probe.http_get.path + port = containers.value.startup_probe.http_get.port + dynamic "http_headers" { + for_each = coalesce(containers.value.startup_probe.http_get.http_headers, tomap({})) + content { + name = http_headers.key + value = http_headers.value + } + } + } + } + dynamic "tcp_socket" { + for_each = containers.value.startup_probe.tcp_socket == null ? [] : [""] + content { + port = containers.value.startup_probe.tcp_socket.port + } + } + dynamic "grpc" { + for_each = containers.value.startup_probe.grpc == null ? [] : [""] + content { + port = containers.value.startup_probe.grpc.port + service = containers.value.startup_probe.grpc.service + } + } + } + } + } + } + dynamic "volumes" { + for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances == null } + content { + name = volumes.key + dynamic "secret" { + for_each = volumes.value.secret == null ? [] : [""] + content { + secret = volumes.value.secret.name + default_mode = volumes.value.secret.default_mode + dynamic "items" { + for_each = volumes.value.secret.path == null ? [] : [""] + content { + path = volumes.value.secret.path + version = volumes.value.secret.version + mode = volumes.value.secret.mode + } + } + } + } + + dynamic "empty_dir" { + for_each = volumes.value.empty_dir_size == null ? [] : [""] + content { + medium = "MEMORY" + size_limit = volumes.value.empty_dir_size + } + } + dynamic "gcs" { + for_each = volumes.value.gcs == null ? [] : [""] + content { + bucket = volumes.value.gcs.bucket + read_only = volumes.value.gcs.is_read_only + } + } + dynamic "nfs" { + for_each = volumes.value.nfs == null ? [] : [""] + content { + server = volumes.value.nfs.server + path = volumes.value.nfs.path + read_only = volumes.value.nfs.is_read_only + } + } + } + } + # CloudSQL is the last volume in the list returned by API + dynamic "volumes" { + for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances != null } + content { + name = volumes.key + dynamic "cloud_sql_instance" { + for_each = length(coalesce(volumes.value.cloud_sql_instances, [])) == 0 ? [] : [""] + content { + instances = volumes.value.cloud_sql_instances + } + } + } + } + } + } + + lifecycle { + ignore_changes = [ + client, + client_version, + template[0].annotations["run.googleapis.com/operation-id"], + template[0].template, + template[0].labels + ] + } +} diff --git a/modules/cloud-run-v2/job.tf b/modules/cloud-run-v2/job.tf index 106482f0d..40ce76425 100644 --- a/modules/cloud-run-v2/job.tf +++ b/modules/cloud-run-v2/job.tf @@ -14,419 +14,11 @@ * limitations under the License. */ -resource "google_cloud_run_v2_job" "job" { - count = var.type == "JOB" && var.managed_revision ? 1 : 0 - provider = google-beta - project = var.project_id - location = var.region - name = var.name - labels = var.labels - launch_stage = var.launch_stage - deletion_protection = var.deletion_protection - template { - labels = var.revision.labels - task_count = var.job_config.task_count - template { - encryption_key = var.encryption_key - gpu_zonal_redundancy_disabled = var.revision.gpu_zonal_redundancy_disabled - dynamic "node_selector" { - for_each = var.revision.node_selector == null ? [] : [""] - content { - accelerator = var.revision.node_selector.accelerator - } - } - dynamic "vpc_access" { - for_each = local.connector == null ? [] : [""] - content { - connector = local.connector - egress = try(var.revision.vpc_access.egress, null) - } - } - dynamic "vpc_access" { - for_each = var.revision.vpc_access.subnet == null && var.revision.vpc_access.network == null ? [] : [""] - content { - egress = var.revision.vpc_access.egress - network_interfaces { - subnetwork = var.revision.vpc_access.subnet - network = var.revision.vpc_access.network - tags = var.revision.vpc_access.tags - } - } - } - max_retries = var.job_config.max_retries - timeout = var.job_config.timeout - service_account = local.service_account_email - dynamic "containers" { - for_each = var.containers - content { - name = containers.key - image = containers.value.image - depends_on = containers.value.depends_on - command = containers.value.command - args = containers.value.args - dynamic "env" { - for_each = coalesce(containers.value.env, tomap({})) - content { - name = env.key - value = env.value - } - } - dynamic "env" { - for_each = coalesce(containers.value.env_from_key, tomap({})) - content { - name = env.key - value_source { - secret_key_ref { - secret = env.value.secret - version = env.value.version - } - } - } - } - dynamic "resources" { - for_each = containers.value.resources == null ? [] : [""] - content { - limits = containers.value.resources.limits - } - } - dynamic "ports" { - for_each = coalesce(containers.value.ports, tomap({})) - content { - container_port = ports.value.container_port - name = ports.value.name - } - } - dynamic "volume_mounts" { - for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k != "cloudsql" } - content { - name = volume_mounts.key - mount_path = volume_mounts.value - } - } - # CloudSQL is the last mount in the list returned by API - dynamic "volume_mounts" { - for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k == "cloudsql" } - content { - name = volume_mounts.key - mount_path = volume_mounts.value - } - } - dynamic "startup_probe" { - for_each = containers.value.startup_probe == null ? [] : [""] - content { - initial_delay_seconds = containers.value.startup_probe.initial_delay_seconds - timeout_seconds = containers.value.startup_probe.timeout_seconds - period_seconds = containers.value.startup_probe.period_seconds - failure_threshold = containers.value.startup_probe.failure_threshold - dynamic "http_get" { - for_each = containers.value.startup_probe.http_get == null ? [] : [""] - content { - path = containers.value.startup_probe.http_get.path - port = containers.value.startup_probe.http_get.port - dynamic "http_headers" { - for_each = coalesce(containers.value.startup_probe.http_get.http_headers, tomap({})) - content { - name = http_headers.key - value = http_headers.value - } - } - } - } - dynamic "tcp_socket" { - for_each = containers.value.startup_probe.tcp_socket == null ? [] : [""] - content { - port = containers.value.startup_probe.tcp_socket.port - } - } - dynamic "grpc" { - for_each = containers.value.startup_probe.grpc == null ? [] : [""] - content { - port = containers.value.startup_probe.grpc.port - service = containers.value.startup_probe.grpc.service - } - } - } - } - } - } - dynamic "volumes" { - for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances == null } - content { - name = volumes.key - dynamic "secret" { - for_each = volumes.value.secret == null ? [] : [""] - content { - secret = volumes.value.secret.name - default_mode = volumes.value.secret.default_mode - dynamic "items" { - for_each = volumes.value.secret.path == null ? [] : [""] - content { - path = volumes.value.secret.path - version = volumes.value.secret.version - mode = volumes.value.secret.mode - } - } - } - } - - dynamic "empty_dir" { - for_each = volumes.value.empty_dir_size == null ? [] : [""] - content { - medium = "MEMORY" - size_limit = volumes.value.empty_dir_size - } - } - dynamic "gcs" { - for_each = volumes.value.gcs == null ? [] : [""] - content { - bucket = volumes.value.gcs.bucket - read_only = volumes.value.gcs.is_read_only - } - } - dynamic "nfs" { - for_each = volumes.value.nfs == null ? [] : [""] - content { - server = volumes.value.nfs.server - path = volumes.value.nfs.path - read_only = volumes.value.nfs.is_read_only - } - } - } - } - # CloudSQL is the last volume in the list returned by API - dynamic "volumes" { - for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances != null } - content { - name = volumes.key - dynamic "cloud_sql_instance" { - for_each = length(coalesce(volumes.value.cloud_sql_instances, [])) == 0 ? [] : [""] - content { - instances = volumes.value.cloud_sql_instances - } - } - } - } - } - } - - lifecycle { - ignore_changes = [ - template[0].annotations["run.googleapis.com/operation-id"], - ] - } -} - -resource "google_cloud_run_v2_job" "job_unmanaged" { - count = var.type == "JOB" && !var.managed_revision ? 1 : 0 - provider = google-beta - project = var.project_id - location = var.region - name = var.name - labels = var.labels - launch_stage = var.launch_stage - deletion_protection = var.deletion_protection - template { - labels = var.revision.labels - task_count = var.job_config.task_count - template { - encryption_key = var.encryption_key - gpu_zonal_redundancy_disabled = var.revision.gpu_zonal_redundancy_disabled - dynamic "node_selector" { - for_each = var.revision.node_selector == null ? [] : [""] - content { - accelerator = var.revision.node_selector.accelerator - } - } - dynamic "vpc_access" { - for_each = local.connector == null ? [] : [""] - content { - connector = local.connector - egress = try(var.revision.vpc_access.egress, null) - } - } - dynamic "vpc_access" { - for_each = var.revision.vpc_access.subnet == null && var.revision.vpc_access.network == null ? [] : [""] - content { - egress = var.revision.vpc_access.egress - network_interfaces { - subnetwork = var.revision.vpc_access.subnet - network = var.revision.vpc_access.network - tags = var.revision.vpc_access.tags - } - } - } - max_retries = var.job_config.max_retries - timeout = var.job_config.timeout - service_account = local.service_account_email - dynamic "containers" { - for_each = var.containers - content { - name = containers.key - image = containers.value.image - depends_on = containers.value.depends_on - command = containers.value.command - args = containers.value.args - dynamic "env" { - for_each = coalesce(containers.value.env, tomap({})) - content { - name = env.key - value = env.value - } - } - dynamic "env" { - for_each = coalesce(containers.value.env_from_key, tomap({})) - content { - name = env.key - value_source { - secret_key_ref { - secret = env.value.secret - version = env.value.version - } - } - } - } - dynamic "resources" { - for_each = containers.value.resources == null ? [] : [""] - content { - limits = containers.value.resources.limits - } - } - dynamic "ports" { - for_each = coalesce(containers.value.ports, tomap({})) - content { - container_port = ports.value.container_port - name = ports.value.name - } - } - dynamic "volume_mounts" { - for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k != "cloudsql" } - content { - name = volume_mounts.key - mount_path = volume_mounts.value - } - } - # CloudSQL is the last mount in the list returned by API - dynamic "volume_mounts" { - for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k == "cloudsql" } - content { - name = volume_mounts.key - mount_path = volume_mounts.value - } - } - dynamic "startup_probe" { - for_each = containers.value.startup_probe == null ? [] : [""] - content { - initial_delay_seconds = containers.value.startup_probe.initial_delay_seconds - timeout_seconds = containers.value.startup_probe.timeout_seconds - period_seconds = containers.value.startup_probe.period_seconds - failure_threshold = containers.value.startup_probe.failure_threshold - dynamic "http_get" { - for_each = containers.value.startup_probe.http_get == null ? [] : [""] - content { - path = containers.value.startup_probe.http_get.path - port = containers.value.startup_probe.http_get.port - dynamic "http_headers" { - for_each = coalesce(containers.value.startup_probe.http_get.http_headers, tomap({})) - content { - name = http_headers.key - value = http_headers.value - } - } - } - } - dynamic "tcp_socket" { - for_each = containers.value.startup_probe.tcp_socket == null ? [] : [""] - content { - port = containers.value.startup_probe.tcp_socket.port - } - } - dynamic "grpc" { - for_each = containers.value.startup_probe.grpc == null ? [] : [""] - content { - port = containers.value.startup_probe.grpc.port - service = containers.value.startup_probe.grpc.service - } - } - } - } - } - } - dynamic "volumes" { - for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances == null } - content { - name = volumes.key - dynamic "secret" { - for_each = volumes.value.secret == null ? [] : [""] - content { - secret = volumes.value.secret.name - default_mode = volumes.value.secret.default_mode - dynamic "items" { - for_each = volumes.value.secret.path == null ? [] : [""] - content { - path = volumes.value.secret.path - version = volumes.value.secret.version - mode = volumes.value.secret.mode - } - } - } - } - - dynamic "empty_dir" { - for_each = volumes.value.empty_dir_size == null ? [] : [""] - content { - medium = "MEMORY" - size_limit = volumes.value.empty_dir_size - } - } - dynamic "gcs" { - for_each = volumes.value.gcs == null ? [] : [""] - content { - bucket = volumes.value.gcs.bucket - read_only = volumes.value.gcs.is_read_only - } - } - dynamic "nfs" { - for_each = volumes.value.nfs == null ? [] : [""] - content { - server = volumes.value.nfs.server - path = volumes.value.nfs.path - read_only = volumes.value.nfs.is_read_only - } - } - } - } - # CloudSQL is the last volume in the list returned by API - dynamic "volumes" { - for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances != null } - content { - name = volumes.key - dynamic "cloud_sql_instance" { - for_each = length(coalesce(volumes.value.cloud_sql_instances, [])) == 0 ? [] : [""] - content { - instances = volumes.value.cloud_sql_instances - } - } - } - } - } - } - - lifecycle { - ignore_changes = [ - client, - client_version, - template[0].annotations["run.googleapis.com/operation-id"], - template[0].template, - template[0].labels - ] - } -} - resource "google_cloud_run_v2_job_iam_binding" "binding" { for_each = var.type == "JOB" ? var.iam : {} project = local.resource.project location = local.resource.location name = local.resource.name - role = each.key - members = each.value + role = lookup(local.ctx.custom_roles, each.key, each.key) + members = [for member in each.value : lookup(local.ctx.iam_principals, member, member)] } diff --git a/modules/cloud-run-v2/main.tf b/modules/cloud-run-v2/main.tf index f0aa07ff1..1ae0186f5 100644 --- a/modules/cloud-run-v2/main.tf +++ b/modules/cloud-run-v2/main.tf @@ -15,6 +15,12 @@ */ locals { + _ctx_p = "$" + ctx = { + for k, v in var.context : k => { + for kk, vv in v : "${local._ctx_p}${k}:${kk}" => vv + } if k != "condition_vars" + } connector = ( var.vpc_connector_create != null ? google_vpc_access_connector.connector[0].id @@ -37,14 +43,12 @@ locals { } invoke_command = local._invoke_command[var.type] + location = lookup(local.ctx.locations, var.region, var.region) + project_id = lookup(local.ctx.project_ids, var.project_id, var.project_id) + revision_name = ( var.revision.name == null ? null : "${var.name}-${var.revision.name}" ) - service_account_email = ( - var.service_account_create - ? google_service_account.service_account[0].email - : var.service_account - ) _resource = { "JOB" : ( var.managed_revision ? @@ -67,82 +71,3 @@ locals { uri = var.type == "SERVICE" ? local._resource[var.type].uri : "" } } - -resource "google_service_account" "service_account" { - count = var.service_account_create ? 1 : 0 - project = var.project_id - account_id = "tf-cr-${var.name}" - display_name = "Terraform Cloud Run ${var.name}." -} - -resource "google_eventarc_trigger" "audit_log_triggers" { - for_each = coalesce(var.service_config.eventarc_triggers.audit_log, tomap({})) - name = "audit-log-${each.key}" - location = google_cloud_run_v2_service.service[0].location - project = google_cloud_run_v2_service.service[0].project - matching_criteria { - attribute = "type" - value = "google.cloud.audit.log.v1.written" - } - matching_criteria { - attribute = "serviceName" - value = each.value.service - } - matching_criteria { - attribute = "methodName" - value = each.value.method - } - destination { - cloud_run_service { - service = google_cloud_run_v2_service.service[0].name - region = google_cloud_run_v2_service.service[0].location - } - } - service_account = var.service_config.eventarc_triggers.service_account_email -} - -resource "google_eventarc_trigger" "pubsub_triggers" { - for_each = coalesce(var.service_config.eventarc_triggers.pubsub, tomap({})) - name = "pubsub-${each.key}" - location = google_cloud_run_v2_service.service[0].location - project = google_cloud_run_v2_service.service[0].project - matching_criteria { - attribute = "type" - value = "google.cloud.pubsub.topic.v1.messagePublished" - } - transport { - pubsub { - topic = each.value - } - } - destination { - cloud_run_service { - service = google_cloud_run_v2_service.service[0].name - region = google_cloud_run_v2_service.service[0].location - } - } - service_account = var.service_config.eventarc_triggers.service_account_email -} - -resource "google_eventarc_trigger" "storage_triggers" { - for_each = coalesce(var.service_config.eventarc_triggers.storage, tomap({})) - name = "storage-${each.key}" - location = google_cloud_run_v2_service.service[0].location - project = google_cloud_run_v2_service.service[0].project - matching_criteria { - attribute = "type" - value = "google.cloud.storage.object.v1.finalized" - } - matching_criteria { - attribute = "bucket" - value = each.value.bucket - } - destination { - cloud_run_service { - service = google_cloud_run_v2_service.service[0].name - region = google_cloud_run_v2_service.service[0].location - path = try(each.value.path, null) - } - } - service_account = var.service_config.eventarc_triggers.service_account_email -} diff --git a/modules/cloud-run-v2/recipes/cloudsql-iam-auth-proxy/README.md b/modules/cloud-run-v2/recipes/cloudsql-iam-auth-proxy/README.md index 788f75e25..04a5d95e0 100644 --- a/modules/cloud-run-v2/recipes/cloudsql-iam-auth-proxy/README.md +++ b/modules/cloud-run-v2/recipes/cloudsql-iam-auth-proxy/README.md @@ -89,13 +89,16 @@ module "database_run" { } } } - service_account = module.run-sa.email - deletion_protection = false + service_account_config = { + create = false + email = module.run-sa.email + } volumes = { custom_cloudsql = { empty_dir_size = "128k" } } + deletion_protection = false } # tftest inventory=recipe-cloudsql-iam-auth-proxy.yaml e2e ``` diff --git a/modules/cloud-run-v2/service-managed.tf b/modules/cloud-run-v2/service-managed.tf new file mode 100644 index 000000000..a6078c13d --- /dev/null +++ b/modules/cloud-run-v2/service-managed.tf @@ -0,0 +1,271 @@ +/** + * 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_cloud_run_v2_service" "service" { + count = var.type == "SERVICE" && var.managed_revision ? 1 : 0 + provider = google-beta + project = local.project_id + location = local.location + name = var.name + ingress = var.service_config.ingress + invoker_iam_disabled = var.service_config.invoker_iam_disabled + labels = var.labels + launch_stage = var.launch_stage + custom_audiences = var.service_config.custom_audiences + deletion_protection = var.deletion_protection + iap_enabled = var.service_config.iap_config != null + + template { + labels = var.revision.labels + encryption_key = var.encryption_key + revision = local.revision_name + execution_environment = ( + var.service_config.gen2_execution_environment + ? "EXECUTION_ENVIRONMENT_GEN2" : "EXECUTION_ENVIRONMENT_GEN1" + ) + gpu_zonal_redundancy_disabled = var.revision.gpu_zonal_redundancy_disabled + max_instance_request_concurrency = var.service_config.max_concurrency + dynamic "node_selector" { + for_each = var.revision.node_selector == null ? [] : [""] + content { + accelerator = var.revision.node_selector.accelerator + } + } + dynamic "scaling" { + for_each = var.service_config.scaling == null ? [] : [""] + content { + max_instance_count = var.service_config.scaling.max_instance_count + min_instance_count = var.service_config.scaling.min_instance_count + } + } + dynamic "vpc_access" { + for_each = local.connector == null ? [] : [""] + content { + connector = local.connector + egress = try(var.revision.vpc_access.egress, null) + } + } + dynamic "vpc_access" { + for_each = var.revision.vpc_access.subnet == null && var.revision.vpc_access.network == null ? [] : [""] + content { + egress = var.revision.vpc_access.egress + network_interfaces { + subnetwork = var.revision.vpc_access.subnet == null ? null : lookup( + local.ctx.subnets, var.revision.vpc_access.subnet, + var.revision.vpc_access.subnet + ) + network = var.revision.vpc_access.network == null ? null : lookup( + local.ctx.networks, var.revision.vpc_access.network, + var.revision.vpc_access.network + ) + tags = var.revision.vpc_access.tags + } + } + } + timeout = var.service_config.timeout + service_account = local.service_account_email + dynamic "containers" { + for_each = var.containers + content { + name = containers.key + image = containers.value.image + depends_on = containers.value.depends_on + command = containers.value.command + args = containers.value.args + dynamic "env" { + for_each = coalesce(containers.value.env, tomap({})) + content { + name = env.key + value = env.value + } + } + dynamic "env" { + for_each = coalesce(containers.value.env_from_key, tomap({})) + content { + name = env.key + value_source { + secret_key_ref { + secret = env.value.secret + version = env.value.version + } + } + } + } + dynamic "resources" { + for_each = containers.value.resources == null ? [] : [""] + content { + limits = containers.value.resources.limits + cpu_idle = containers.value.resources.cpu_idle + startup_cpu_boost = containers.value.resources.startup_cpu_boost + } + } + dynamic "ports" { + for_each = coalesce(containers.value.ports, tomap({})) + content { + container_port = ports.value.container_port + name = ports.value.name + } + } + dynamic "volume_mounts" { + for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k != "cloudsql" } + content { + name = volume_mounts.key + mount_path = volume_mounts.value + } + } + # CloudSQL is the last mount in the list returned by API + dynamic "volume_mounts" { + for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k == "cloudsql" } + content { + name = volume_mounts.key + mount_path = volume_mounts.value + } + } + dynamic "liveness_probe" { + for_each = containers.value.liveness_probe == null ? [] : [""] + content { + initial_delay_seconds = containers.value.liveness_probe.initial_delay_seconds + timeout_seconds = containers.value.liveness_probe.timeout_seconds + period_seconds = containers.value.liveness_probe.period_seconds + failure_threshold = containers.value.liveness_probe.failure_threshold + dynamic "http_get" { + for_each = containers.value.liveness_probe.http_get == null ? [] : [""] + content { + path = containers.value.liveness_probe.http_get.path + port = containers.value.liveness_probe.http_get.port + dynamic "http_headers" { + for_each = coalesce(containers.value.liveness_probe.http_get.http_headers, tomap({})) + content { + name = http_headers.key + value = http_headers.value + } + } + } + } + dynamic "grpc" { + for_each = containers.value.liveness_probe.grpc == null ? [] : [""] + content { + port = containers.value.liveness_probe.grpc.port + service = containers.value.liveness_probe.grpc.service + } + } + } + } + dynamic "startup_probe" { + for_each = containers.value.startup_probe == null ? [] : [""] + content { + initial_delay_seconds = containers.value.startup_probe.initial_delay_seconds + timeout_seconds = containers.value.startup_probe.timeout_seconds + period_seconds = containers.value.startup_probe.period_seconds + failure_threshold = containers.value.startup_probe.failure_threshold + dynamic "http_get" { + for_each = containers.value.startup_probe.http_get == null ? [] : [""] + content { + path = containers.value.startup_probe.http_get.path + port = containers.value.startup_probe.http_get.port + dynamic "http_headers" { + for_each = coalesce(containers.value.startup_probe.http_get.http_headers, tomap({})) + content { + name = http_headers.key + value = http_headers.value + } + } + } + } + dynamic "tcp_socket" { + for_each = containers.value.startup_probe.tcp_socket == null ? [] : [""] + content { + port = containers.value.startup_probe.tcp_socket.port + } + } + dynamic "grpc" { + for_each = containers.value.startup_probe.grpc == null ? [] : [""] + content { + port = containers.value.startup_probe.grpc.port + service = containers.value.startup_probe.grpc.service + } + } + } + } + } + } + dynamic "volumes" { + for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances == null } + content { + name = volumes.key + dynamic "secret" { + for_each = volumes.value.secret == null ? [] : [""] + content { + secret = volumes.value.secret.name + default_mode = volumes.value.secret.default_mode + dynamic "items" { + for_each = volumes.value.secret.path == null ? [] : [""] + content { + path = volumes.value.secret.path + version = volumes.value.secret.version + mode = volumes.value.secret.mode + } + } + } + } + + dynamic "empty_dir" { + for_each = volumes.value.empty_dir_size == null ? [] : [""] + content { + medium = "MEMORY" + size_limit = volumes.value.empty_dir_size + } + } + dynamic "gcs" { + for_each = volumes.value.gcs == null ? [] : [""] + content { + bucket = volumes.value.gcs.bucket + read_only = volumes.value.gcs.is_read_only + } + } + dynamic "nfs" { + for_each = volumes.value.nfs == null ? [] : [""] + content { + server = volumes.value.nfs.server + path = volumes.value.nfs.path + read_only = volumes.value.nfs.is_read_only + } + } + } + } + # CloudSQL is the last volume in the list returned by API + dynamic "volumes" { + for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances != null } + content { + name = volumes.key + dynamic "cloud_sql_instance" { + for_each = length(coalesce(volumes.value.cloud_sql_instances, [])) == 0 ? [] : [""] + content { + instances = volumes.value.cloud_sql_instances + } + } + } + } + } + + lifecycle { + ignore_changes = [ + client, + client_version, + template[0].annotations["run.googleapis.com/operation-id"], + ] + } +} diff --git a/modules/cloud-run-v2/service-unmanaged.tf b/modules/cloud-run-v2/service-unmanaged.tf new file mode 100644 index 000000000..5aaa50005 --- /dev/null +++ b/modules/cloud-run-v2/service-unmanaged.tf @@ -0,0 +1,274 @@ +/** + * 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_cloud_run_v2_service" "service_unmanaged" { + count = var.type == "SERVICE" && !var.managed_revision ? 1 : 0 + provider = google-beta + project = local.project_id + location = local.location + name = var.name + ingress = var.service_config.ingress + invoker_iam_disabled = var.service_config.invoker_iam_disabled + labels = var.labels + launch_stage = var.launch_stage + custom_audiences = var.service_config.custom_audiences + deletion_protection = var.deletion_protection + iap_enabled = var.service_config.iap_config != null + + template { + labels = var.revision.labels + encryption_key = var.encryption_key + revision = local.revision_name + execution_environment = ( + var.service_config.gen2_execution_environment + ? "EXECUTION_ENVIRONMENT_GEN2" : "EXECUTION_ENVIRONMENT_GEN1" + ) + gpu_zonal_redundancy_disabled = var.revision.gpu_zonal_redundancy_disabled + max_instance_request_concurrency = var.service_config.max_concurrency + dynamic "node_selector" { + for_each = var.revision.node_selector == null ? [] : [""] + content { + accelerator = var.revision.node_selector.accelerator + } + } + dynamic "scaling" { + for_each = var.service_config.scaling == null ? [] : [""] + content { + max_instance_count = var.service_config.scaling.max_instance_count + min_instance_count = var.service_config.scaling.min_instance_count + } + } + dynamic "vpc_access" { + for_each = local.connector == null ? [] : [""] + content { + connector = local.connector + egress = try(var.revision.vpc_access.egress, null) + } + } + dynamic "vpc_access" { + for_each = var.revision.vpc_access.subnet == null && var.revision.vpc_access.network == null ? [] : [""] + content { + egress = var.revision.vpc_access.egress + network_interfaces { + subnetwork = var.revision.vpc_access.subnet == null ? null : lookup( + local.ctx.subnets, var.revision.vpc_access.subnet, + var.revision.vpc_access.subnet + ) + network = var.revision.vpc_access.network == null ? null : lookup( + local.ctx.networks, var.revision.vpc_access.network, + var.revision.vpc_access.network + ) + tags = var.revision.vpc_access.tags + } + } + } + timeout = var.service_config.timeout + service_account = local.service_account_email + dynamic "containers" { + for_each = var.containers + content { + name = containers.key + image = containers.value.image + depends_on = containers.value.depends_on + command = containers.value.command + args = containers.value.args + dynamic "env" { + for_each = coalesce(containers.value.env, tomap({})) + content { + name = env.key + value = env.value + } + } + dynamic "env" { + for_each = coalesce(containers.value.env_from_key, tomap({})) + content { + name = env.key + value_source { + secret_key_ref { + secret = env.value.secret + version = env.value.version + } + } + } + } + dynamic "resources" { + for_each = containers.value.resources == null ? [] : [""] + content { + limits = containers.value.resources.limits + cpu_idle = containers.value.resources.cpu_idle + startup_cpu_boost = containers.value.resources.startup_cpu_boost + } + } + dynamic "ports" { + for_each = coalesce(containers.value.ports, tomap({})) + content { + container_port = ports.value.container_port + name = ports.value.name + } + } + dynamic "volume_mounts" { + for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k != "cloudsql" } + content { + name = volume_mounts.key + mount_path = volume_mounts.value + } + } + # CloudSQL is the last mount in the list returned by API + dynamic "volume_mounts" { + for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k == "cloudsql" } + content { + name = volume_mounts.key + mount_path = volume_mounts.value + } + } + dynamic "liveness_probe" { + for_each = containers.value.liveness_probe == null ? [] : [""] + content { + initial_delay_seconds = containers.value.liveness_probe.initial_delay_seconds + timeout_seconds = containers.value.liveness_probe.timeout_seconds + period_seconds = containers.value.liveness_probe.period_seconds + failure_threshold = containers.value.liveness_probe.failure_threshold + dynamic "http_get" { + for_each = containers.value.liveness_probe.http_get == null ? [] : [""] + content { + path = containers.value.liveness_probe.http_get.path + port = containers.value.liveness_probe.http_get.port + dynamic "http_headers" { + for_each = coalesce(containers.value.liveness_probe.http_get.http_headers, tomap({})) + content { + name = http_headers.key + value = http_headers.value + } + } + } + } + dynamic "grpc" { + for_each = containers.value.liveness_probe.grpc == null ? [] : [""] + content { + port = containers.value.liveness_probe.grpc.port + service = containers.value.liveness_probe.grpc.service + } + } + } + } + dynamic "startup_probe" { + for_each = containers.value.startup_probe == null ? [] : [""] + content { + initial_delay_seconds = containers.value.startup_probe.initial_delay_seconds + timeout_seconds = containers.value.startup_probe.timeout_seconds + period_seconds = containers.value.startup_probe.period_seconds + failure_threshold = containers.value.startup_probe.failure_threshold + dynamic "http_get" { + for_each = containers.value.startup_probe.http_get == null ? [] : [""] + content { + path = containers.value.startup_probe.http_get.path + port = containers.value.startup_probe.http_get.port + dynamic "http_headers" { + for_each = coalesce(containers.value.startup_probe.http_get.http_headers, tomap({})) + content { + name = http_headers.key + value = http_headers.value + } + } + } + } + dynamic "tcp_socket" { + for_each = containers.value.startup_probe.tcp_socket == null ? [] : [""] + content { + port = containers.value.startup_probe.tcp_socket.port + } + } + dynamic "grpc" { + for_each = containers.value.startup_probe.grpc == null ? [] : [""] + content { + port = containers.value.startup_probe.grpc.port + service = containers.value.startup_probe.grpc.service + } + } + } + } + } + } + dynamic "volumes" { + for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances == null } + content { + name = volumes.key + dynamic "secret" { + for_each = volumes.value.secret == null ? [] : [""] + content { + secret = volumes.value.secret.name + default_mode = volumes.value.secret.default_mode + dynamic "items" { + for_each = volumes.value.secret.path == null ? [] : [""] + content { + path = volumes.value.secret.path + version = volumes.value.secret.version + mode = volumes.value.secret.mode + } + } + } + } + + dynamic "empty_dir" { + for_each = volumes.value.empty_dir_size == null ? [] : [""] + content { + medium = "MEMORY" + size_limit = volumes.value.empty_dir_size + } + } + dynamic "gcs" { + for_each = volumes.value.gcs == null ? [] : [""] + content { + bucket = volumes.value.gcs.bucket + read_only = volumes.value.gcs.is_read_only + } + } + dynamic "nfs" { + for_each = volumes.value.nfs == null ? [] : [""] + content { + server = volumes.value.nfs.server + path = volumes.value.nfs.path + read_only = volumes.value.nfs.is_read_only + } + } + } + } + # CloudSQL is the last volume in the list returned by API + dynamic "volumes" { + for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances != null } + content { + name = volumes.key + dynamic "cloud_sql_instance" { + for_each = length(coalesce(volumes.value.cloud_sql_instances, [])) == 0 ? [] : [""] + content { + instances = volumes.value.cloud_sql_instances + } + } + } + } + } + + lifecycle { + ignore_changes = [ + build_config, + client, + client_version, + template[0].annotations["run.googleapis.com/operation-id"], + template[0].containers, + template[0].labels + ] + } +} diff --git a/modules/cloud-run-v2/service.tf b/modules/cloud-run-v2/service.tf index 0d0de99e4..bb15a9ee9 100644 --- a/modules/cloud-run-v2/service.tf +++ b/modules/cloud-run-v2/service.tf @@ -14,516 +14,13 @@ * limitations under the License. */ -resource "google_cloud_run_v2_service" "service" { - count = var.type == "SERVICE" && var.managed_revision ? 1 : 0 - provider = google-beta - project = var.project_id - location = var.region - name = var.name - ingress = var.service_config.ingress - invoker_iam_disabled = var.service_config.invoker_iam_disabled - labels = var.labels - launch_stage = var.launch_stage - custom_audiences = var.service_config.custom_audiences - deletion_protection = var.deletion_protection - iap_enabled = var.service_config.iap_config != null - - template { - labels = var.revision.labels - encryption_key = var.encryption_key - revision = local.revision_name - execution_environment = ( - var.service_config.gen2_execution_environment - ? "EXECUTION_ENVIRONMENT_GEN2" : "EXECUTION_ENVIRONMENT_GEN1" - ) - gpu_zonal_redundancy_disabled = var.revision.gpu_zonal_redundancy_disabled - max_instance_request_concurrency = var.service_config.max_concurrency - dynamic "node_selector" { - for_each = var.revision.node_selector == null ? [] : [""] - content { - accelerator = var.revision.node_selector.accelerator - } - } - dynamic "scaling" { - for_each = var.service_config.scaling == null ? [] : [""] - content { - max_instance_count = var.service_config.scaling.max_instance_count - min_instance_count = var.service_config.scaling.min_instance_count - } - } - dynamic "vpc_access" { - for_each = local.connector == null ? [] : [""] - content { - connector = local.connector - egress = try(var.revision.vpc_access.egress, null) - } - } - dynamic "vpc_access" { - for_each = var.revision.vpc_access.subnet == null && var.revision.vpc_access.network == null ? [] : [""] - content { - egress = var.revision.vpc_access.egress - network_interfaces { - subnetwork = var.revision.vpc_access.subnet - network = var.revision.vpc_access.network - tags = var.revision.vpc_access.tags - } - } - } - timeout = var.service_config.timeout - service_account = local.service_account_email - dynamic "containers" { - for_each = var.containers - content { - name = containers.key - image = containers.value.image - depends_on = containers.value.depends_on - command = containers.value.command - args = containers.value.args - dynamic "env" { - for_each = coalesce(containers.value.env, tomap({})) - content { - name = env.key - value = env.value - } - } - dynamic "env" { - for_each = coalesce(containers.value.env_from_key, tomap({})) - content { - name = env.key - value_source { - secret_key_ref { - secret = env.value.secret - version = env.value.version - } - } - } - } - dynamic "resources" { - for_each = containers.value.resources == null ? [] : [""] - content { - limits = containers.value.resources.limits - cpu_idle = containers.value.resources.cpu_idle - startup_cpu_boost = containers.value.resources.startup_cpu_boost - } - } - dynamic "ports" { - for_each = coalesce(containers.value.ports, tomap({})) - content { - container_port = ports.value.container_port - name = ports.value.name - } - } - dynamic "volume_mounts" { - for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k != "cloudsql" } - content { - name = volume_mounts.key - mount_path = volume_mounts.value - } - } - # CloudSQL is the last mount in the list returned by API - dynamic "volume_mounts" { - for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k == "cloudsql" } - content { - name = volume_mounts.key - mount_path = volume_mounts.value - } - } - dynamic "liveness_probe" { - for_each = containers.value.liveness_probe == null ? [] : [""] - content { - initial_delay_seconds = containers.value.liveness_probe.initial_delay_seconds - timeout_seconds = containers.value.liveness_probe.timeout_seconds - period_seconds = containers.value.liveness_probe.period_seconds - failure_threshold = containers.value.liveness_probe.failure_threshold - dynamic "http_get" { - for_each = containers.value.liveness_probe.http_get == null ? [] : [""] - content { - path = containers.value.liveness_probe.http_get.path - port = containers.value.liveness_probe.http_get.port - dynamic "http_headers" { - for_each = coalesce(containers.value.liveness_probe.http_get.http_headers, tomap({})) - content { - name = http_headers.key - value = http_headers.value - } - } - } - } - dynamic "grpc" { - for_each = containers.value.liveness_probe.grpc == null ? [] : [""] - content { - port = containers.value.liveness_probe.grpc.port - service = containers.value.liveness_probe.grpc.service - } - } - } - } - dynamic "startup_probe" { - for_each = containers.value.startup_probe == null ? [] : [""] - content { - initial_delay_seconds = containers.value.startup_probe.initial_delay_seconds - timeout_seconds = containers.value.startup_probe.timeout_seconds - period_seconds = containers.value.startup_probe.period_seconds - failure_threshold = containers.value.startup_probe.failure_threshold - dynamic "http_get" { - for_each = containers.value.startup_probe.http_get == null ? [] : [""] - content { - path = containers.value.startup_probe.http_get.path - port = containers.value.startup_probe.http_get.port - dynamic "http_headers" { - for_each = coalesce(containers.value.startup_probe.http_get.http_headers, tomap({})) - content { - name = http_headers.key - value = http_headers.value - } - } - } - } - dynamic "tcp_socket" { - for_each = containers.value.startup_probe.tcp_socket == null ? [] : [""] - content { - port = containers.value.startup_probe.tcp_socket.port - } - } - dynamic "grpc" { - for_each = containers.value.startup_probe.grpc == null ? [] : [""] - content { - port = containers.value.startup_probe.grpc.port - service = containers.value.startup_probe.grpc.service - } - } - } - } - } - } - dynamic "volumes" { - for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances == null } - content { - name = volumes.key - dynamic "secret" { - for_each = volumes.value.secret == null ? [] : [""] - content { - secret = volumes.value.secret.name - default_mode = volumes.value.secret.default_mode - dynamic "items" { - for_each = volumes.value.secret.path == null ? [] : [""] - content { - path = volumes.value.secret.path - version = volumes.value.secret.version - mode = volumes.value.secret.mode - } - } - } - } - - dynamic "empty_dir" { - for_each = volumes.value.empty_dir_size == null ? [] : [""] - content { - medium = "MEMORY" - size_limit = volumes.value.empty_dir_size - } - } - dynamic "gcs" { - for_each = volumes.value.gcs == null ? [] : [""] - content { - bucket = volumes.value.gcs.bucket - read_only = volumes.value.gcs.is_read_only - } - } - dynamic "nfs" { - for_each = volumes.value.nfs == null ? [] : [""] - content { - server = volumes.value.nfs.server - path = volumes.value.nfs.path - read_only = volumes.value.nfs.is_read_only - } - } - } - } - # CloudSQL is the last volume in the list returned by API - dynamic "volumes" { - for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances != null } - content { - name = volumes.key - dynamic "cloud_sql_instance" { - for_each = length(coalesce(volumes.value.cloud_sql_instances, [])) == 0 ? [] : [""] - content { - instances = volumes.value.cloud_sql_instances - } - } - } - } - } - - lifecycle { - ignore_changes = [ - client, - client_version, - template[0].annotations["run.googleapis.com/operation-id"], - ] - } -} - -resource "google_cloud_run_v2_service" "service_unmanaged" { - count = var.type == "SERVICE" && !var.managed_revision ? 1 : 0 - provider = google-beta - project = var.project_id - location = var.region - name = var.name - ingress = var.service_config.ingress - invoker_iam_disabled = var.service_config.invoker_iam_disabled - labels = var.labels - launch_stage = var.launch_stage - custom_audiences = var.service_config.custom_audiences - deletion_protection = var.deletion_protection - iap_enabled = var.service_config.iap_config != null - - template { - labels = var.revision.labels - encryption_key = var.encryption_key - revision = local.revision_name - execution_environment = ( - var.service_config.gen2_execution_environment - ? "EXECUTION_ENVIRONMENT_GEN2" : "EXECUTION_ENVIRONMENT_GEN1" - ) - gpu_zonal_redundancy_disabled = var.revision.gpu_zonal_redundancy_disabled - max_instance_request_concurrency = var.service_config.max_concurrency - dynamic "node_selector" { - for_each = var.revision.node_selector == null ? [] : [""] - content { - accelerator = var.revision.node_selector.accelerator - } - } - dynamic "scaling" { - for_each = var.service_config.scaling == null ? [] : [""] - content { - max_instance_count = var.service_config.scaling.max_instance_count - min_instance_count = var.service_config.scaling.min_instance_count - } - } - dynamic "vpc_access" { - for_each = local.connector == null ? [] : [""] - content { - connector = local.connector - egress = try(var.revision.vpc_access.egress, null) - } - } - dynamic "vpc_access" { - for_each = var.revision.vpc_access.subnet == null && var.revision.vpc_access.network == null ? [] : [""] - content { - egress = var.revision.vpc_access.egress - network_interfaces { - subnetwork = var.revision.vpc_access.subnet - network = var.revision.vpc_access.network - tags = var.revision.vpc_access.tags - } - } - } - timeout = var.service_config.timeout - service_account = local.service_account_email - dynamic "containers" { - for_each = var.containers - content { - name = containers.key - image = containers.value.image - depends_on = containers.value.depends_on - command = containers.value.command - args = containers.value.args - dynamic "env" { - for_each = coalesce(containers.value.env, tomap({})) - content { - name = env.key - value = env.value - } - } - dynamic "env" { - for_each = coalesce(containers.value.env_from_key, tomap({})) - content { - name = env.key - value_source { - secret_key_ref { - secret = env.value.secret - version = env.value.version - } - } - } - } - dynamic "resources" { - for_each = containers.value.resources == null ? [] : [""] - content { - limits = containers.value.resources.limits - cpu_idle = containers.value.resources.cpu_idle - startup_cpu_boost = containers.value.resources.startup_cpu_boost - } - } - dynamic "ports" { - for_each = coalesce(containers.value.ports, tomap({})) - content { - container_port = ports.value.container_port - name = ports.value.name - } - } - dynamic "volume_mounts" { - for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k != "cloudsql" } - content { - name = volume_mounts.key - mount_path = volume_mounts.value - } - } - # CloudSQL is the last mount in the list returned by API - dynamic "volume_mounts" { - for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k == "cloudsql" } - content { - name = volume_mounts.key - mount_path = volume_mounts.value - } - } - dynamic "liveness_probe" { - for_each = containers.value.liveness_probe == null ? [] : [""] - content { - initial_delay_seconds = containers.value.liveness_probe.initial_delay_seconds - timeout_seconds = containers.value.liveness_probe.timeout_seconds - period_seconds = containers.value.liveness_probe.period_seconds - failure_threshold = containers.value.liveness_probe.failure_threshold - dynamic "http_get" { - for_each = containers.value.liveness_probe.http_get == null ? [] : [""] - content { - path = containers.value.liveness_probe.http_get.path - port = containers.value.liveness_probe.http_get.port - dynamic "http_headers" { - for_each = coalesce(containers.value.liveness_probe.http_get.http_headers, tomap({})) - content { - name = http_headers.key - value = http_headers.value - } - } - } - } - dynamic "grpc" { - for_each = containers.value.liveness_probe.grpc == null ? [] : [""] - content { - port = containers.value.liveness_probe.grpc.port - service = containers.value.liveness_probe.grpc.service - } - } - } - } - dynamic "startup_probe" { - for_each = containers.value.startup_probe == null ? [] : [""] - content { - initial_delay_seconds = containers.value.startup_probe.initial_delay_seconds - timeout_seconds = containers.value.startup_probe.timeout_seconds - period_seconds = containers.value.startup_probe.period_seconds - failure_threshold = containers.value.startup_probe.failure_threshold - dynamic "http_get" { - for_each = containers.value.startup_probe.http_get == null ? [] : [""] - content { - path = containers.value.startup_probe.http_get.path - port = containers.value.startup_probe.http_get.port - dynamic "http_headers" { - for_each = coalesce(containers.value.startup_probe.http_get.http_headers, tomap({})) - content { - name = http_headers.key - value = http_headers.value - } - } - } - } - dynamic "tcp_socket" { - for_each = containers.value.startup_probe.tcp_socket == null ? [] : [""] - content { - port = containers.value.startup_probe.tcp_socket.port - } - } - dynamic "grpc" { - for_each = containers.value.startup_probe.grpc == null ? [] : [""] - content { - port = containers.value.startup_probe.grpc.port - service = containers.value.startup_probe.grpc.service - } - } - } - } - } - } - dynamic "volumes" { - for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances == null } - content { - name = volumes.key - dynamic "secret" { - for_each = volumes.value.secret == null ? [] : [""] - content { - secret = volumes.value.secret.name - default_mode = volumes.value.secret.default_mode - dynamic "items" { - for_each = volumes.value.secret.path == null ? [] : [""] - content { - path = volumes.value.secret.path - version = volumes.value.secret.version - mode = volumes.value.secret.mode - } - } - } - } - - dynamic "empty_dir" { - for_each = volumes.value.empty_dir_size == null ? [] : [""] - content { - medium = "MEMORY" - size_limit = volumes.value.empty_dir_size - } - } - dynamic "gcs" { - for_each = volumes.value.gcs == null ? [] : [""] - content { - bucket = volumes.value.gcs.bucket - read_only = volumes.value.gcs.is_read_only - } - } - dynamic "nfs" { - for_each = volumes.value.nfs == null ? [] : [""] - content { - server = volumes.value.nfs.server - path = volumes.value.nfs.path - read_only = volumes.value.nfs.is_read_only - } - } - } - } - # CloudSQL is the last volume in the list returned by API - dynamic "volumes" { - for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances != null } - content { - name = volumes.key - dynamic "cloud_sql_instance" { - for_each = length(coalesce(volumes.value.cloud_sql_instances, [])) == 0 ? [] : [""] - content { - instances = volumes.value.cloud_sql_instances - } - } - } - } - } - - lifecycle { - ignore_changes = [ - build_config, - client, - client_version, - template[0].annotations["run.googleapis.com/operation-id"], - template[0].containers, - template[0].labels - ] - } -} - resource "google_cloud_run_v2_service_iam_binding" "binding" { for_each = var.type == "SERVICE" ? var.iam : {} project = local.resource.project location = local.resource.location name = local.resource.name - role = each.key - members = each.value + role = lookup(local.ctx.custom_roles, each.key, each.key) + members = [for member in each.value : lookup(local.ctx.iam_principals, member, member)] } resource "google_iap_web_cloud_run_service_iam_member" "member" { @@ -532,7 +29,7 @@ resource "google_iap_web_cloud_run_service_iam_member" "member" { location = local.resource.location cloud_run_service_name = local.resource.name role = "roles/iap.httpsResourceAccessor" - member = each.key + member = lookup(local.ctx.iam_principals, each.key, each.key) } resource "google_iap_web_cloud_run_service_iam_binding" "binding" { @@ -544,5 +41,78 @@ resource "google_iap_web_cloud_run_service_iam_binding" "binding" { location = local.resource.location cloud_run_service_name = local.resource.name role = "roles/iap.httpsResourceAccessor" - members = var.service_config.iap_config.iam + members = [for member in var.service_config.iap_config.iam : lookup(local.ctx.iam_principals, member, member)] +} + +# Event ARC for Cloud Run services +resource "google_eventarc_trigger" "audit_log_triggers" { + for_each = coalesce(var.service_config.eventarc_triggers.audit_log, tomap({})) + name = "audit-log-${each.key}" + location = local.resource.location + project = local.resource.project + matching_criteria { + attribute = "type" + value = "google.cloud.audit.log.v1.written" + } + matching_criteria { + attribute = "serviceName" + value = each.value.service + } + matching_criteria { + attribute = "methodName" + value = each.value.method + } + destination { + cloud_run_service { + service = local.resource.name + region = local.resource.location + } + } + service_account = var.service_config.eventarc_triggers.service_account_email +} + +resource "google_eventarc_trigger" "pubsub_triggers" { + for_each = coalesce(var.service_config.eventarc_triggers.pubsub, tomap({})) + name = "pubsub-${each.key}" + location = local.resource.location + project = local.resource.project + matching_criteria { + attribute = "type" + value = "google.cloud.pubsub.topic.v1.messagePublished" + } + transport { + pubsub { + topic = each.value + } + } + destination { + cloud_run_service { + service = local.resource.name + region = local.resource.location + } + } + service_account = var.service_config.eventarc_triggers.service_account_email +} + +resource "google_eventarc_trigger" "storage_triggers" { + for_each = coalesce(var.service_config.eventarc_triggers.storage, tomap({})) + name = "storage-${each.key}" + location = local.resource.location + project = local.resource.project + matching_criteria { + attribute = "type" + value = "google.cloud.storage.object.v1.finalized" + } + matching_criteria { + attribute = "bucket" + value = each.value.bucket + } + destination { + cloud_run_service { + service = local.resource.name + region = local.resource.location + path = try(each.value.path, null) + } + } + service_account = var.service_config.eventarc_triggers.service_account_email } diff --git a/modules/cloud-run-v2/serviceaccount.tf b/modules/cloud-run-v2/serviceaccount.tf new file mode 100644 index 000000000..f1fdec866 --- /dev/null +++ b/modules/cloud-run-v2/serviceaccount.tf @@ -0,0 +1,53 @@ +/** + * 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 { + service_account_email = ( + var.service_account_config.create + ? google_service_account.service_account[0].email + : lookup( + local.ctx.iam_principals, + var.service_account_config.email, + var.service_account_config.email + ) + ) + service_account_roles = [ + for role in var.service_account_config.roles + : lookup(local.ctx.custom_roles, role, role) + ] +} + +resource "google_service_account" "service_account" { + count = var.service_account_config.create ? 1 : 0 + project = local.project_id + account_id = coalesce(var.service_account_config.name, var.name) + display_name = coalesce( + var.service_account_config.display_name, + var.service_account_config.name, + var.name + ) +} + +resource "google_project_iam_member" "default" { + for_each = ( + var.service_account_config.create + ? toset(local.service_account_roles) + : toset([]) + ) + role = each.key + project = local.project_id + member = google_service_account.service_account[0].member +} diff --git a/modules/cloud-run-v2/tags.tf b/modules/cloud-run-v2/tags.tf index 67b3c68a4..b461d1388 100644 --- a/modules/cloud-run-v2/tags.tf +++ b/modules/cloud-run-v2/tags.tf @@ -25,8 +25,8 @@ locals { resource "google_tags_location_tag_binding" "binding" { for_each = var.tag_bindings parent = ( - "//run.googleapis.com/projects/${var.project_id}/locations/${var.region}/${local.resource_types[var.type]}/${local.resource.name}" + "//run.googleapis.com/projects/${local.project_id}/locations/${local.location}/${local.resource_types[var.type]}/${local.resource.name}" ) - tag_value = each.value - location = var.region + tag_value = lookup(local.ctx.tag_values, each.value, each.value) + location = local.location } diff --git a/modules/cloud-run-v2/variables-serviceaccount.tf b/modules/cloud-run-v2/variables-serviceaccount.tf new file mode 100644 index 000000000..878feed9f --- /dev/null +++ b/modules/cloud-run-v2/variables-serviceaccount.tf @@ -0,0 +1,31 @@ +/** + * 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 "service_account_config" { + description = "Service account configurations." + type = object({ + create = optional(bool, true) + display_name = optional(string) + email = optional(string) + name = optional(string) + roles = optional(list(string), [ + "roles/logging.logWriter", + "roles/monitoring.metricWriter" + ]) + }) + nullable = false + default = {} +} diff --git a/modules/cloud-run-v2/variables-vpcconnector.tf b/modules/cloud-run-v2/variables-vpcconnector.tf index 0ca5ea2cd..e45ebe20e 100644 --- a/modules/cloud-run-v2/variables-vpcconnector.tf +++ b/modules/cloud-run-v2/variables-vpcconnector.tf @@ -15,7 +15,7 @@ */ variable "vpc_connector_create" { - description = "Populate this to create a Serverless VPC Access connector." + description = "VPC connector network configuration. Must be provided if new VPC connector is being created." type = object({ ip_cidr_range = optional(string) machine_type = optional(string) @@ -37,4 +37,12 @@ variable "vpc_connector_create" { }), {}) }) default = null + validation { + condition = ( + var.vpc_connector_create == null || + try(var.vpc_connector_create.instances, null) != null || + try(var.vpc_connector_create.throughput, null) != null + ) + error_message = "VPC connector must specify either instances or throughput." + } } diff --git a/modules/cloud-run-v2/variables.tf b/modules/cloud-run-v2/variables.tf index 4472e94d3..2f78c9ee2 100644 --- a/modules/cloud-run-v2/variables.tf +++ b/modules/cloud-run-v2/variables.tf @@ -94,6 +94,24 @@ variable "containers" { } } +variable "context" { + description = "Context-specific interpolations." + type = object({ + condition_vars = optional(map(map(string)), {}) # not needed here? + cidr_ranges = optional(map(string), {}) + custom_roles = optional(map(string), {}) + iam_principals = optional(map(string), {}) + kms_keys = optional(map(string), {}) + locations = optional(map(string), {}) + networks = optional(map(string), {}) + project_ids = optional(map(string), {}) + subnets = optional(map(string), {}) + tag_values = optional(map(string), {}) + }) + nullable = false + default = {} +} + variable "deletion_protection" { description = "Deletion protection setting for this Cloud Run service." type = string @@ -231,18 +249,12 @@ variable "revision" { ) error_message = "When providing vpc_access.network provide also vpc_access.subnet." } -} - -variable "service_account" { - description = "Service account email. Unused if service account is auto-created." - type = string - default = null -} - -variable "service_account_create" { - description = "Auto-create service account." - type = bool - default = false + validation { + condition = try(var.revision.vpc_access.connector, null) == null || ( + try(var.revision.vpc_access.connector, null) != null && var.vpc_connector_create == null + ) + error_message = "Either provide connector to create in var.vpc_connector_create or provide externally managed connector in var.revision.vpc_access.connector" + } } variable "service_config" { diff --git a/modules/cloud-run-v2/vpcconnector.tf b/modules/cloud-run-v2/vpcconnector.tf index ea135c6d6..2c449132a 100644 --- a/modules/cloud-run-v2/vpcconnector.tf +++ b/modules/cloud-run-v2/vpcconnector.tf @@ -14,17 +14,51 @@ * limitations under the License. */ +locals { + _connector_subnet_name_ctx = ( + try(var.vpc_connector_create.subnet.name, null) == null ? false : + contains(keys(local.ctx.subnets), var.vpc_connector_create.subnet.name) + ) + # if you pass the subnet, you must pass only the name, not the whole id + _connector_subnet_name = ( + local._connector_subnet_name_ctx + ? reverse(split("/", local.ctx.subnets[var.vpc_connector_create.subnet.name]))[0] + : try(var.vpc_connector_create.subnet.name, null) + ) + # if project is not provided, but subnet is coming from context, use project from subnet id in context + # and avoid lookups using null project + _connector_subnet_project_input = try(var.vpc_connector_create.subnet.project_id, null) + _connector_subnet_project = ( + local._connector_subnet_project_input == null + ? ( + local._connector_subnet_name_ctx + ? split("/", local.ctx.subnets[var.vpc_connector_create.subnet.name])[1] + : null + ) + : lookup( + local.ctx.project_ids, local._connector_subnet_project_input, + local._connector_subnet_project_input + ) + ) +} + resource "google_vpc_access_connector" "connector" { count = var.vpc_connector_create != null ? 1 : 0 - project = var.project_id + project = local.project_id name = ( var.vpc_connector_create.name != null ? var.vpc_connector_create.name : var.name ) - region = var.region - ip_cidr_range = var.vpc_connector_create.ip_cidr_range - network = var.vpc_connector_create.network + region = local.location + ip_cidr_range = var.vpc_connector_create.ip_cidr_range == null ? null : lookup( + local.ctx.cidr_ranges, var.vpc_connector_create.ip_cidr_range, + var.vpc_connector_create.ip_cidr_range + ) + network = var.vpc_connector_create.network == null ? null : lookup( + local.ctx.networks, var.vpc_connector_create.network, + var.vpc_connector_create.network + ) machine_type = var.vpc_connector_create.machine_type max_instances = var.vpc_connector_create.instances.max max_throughput = var.vpc_connector_create.throughput.max @@ -33,9 +67,8 @@ resource "google_vpc_access_connector" "connector" { dynamic "subnet" { for_each = var.vpc_connector_create.subnet.name == null ? [] : [""] content { - name = var.vpc_connector_create.subnet.name - project_id = var.vpc_connector_create.subnet.project_id + name = local._connector_subnet_name + project_id = local._connector_subnet_project } } } - diff --git a/modules/cloud-run-v2/workerpool-managed.tf b/modules/cloud-run-v2/workerpool-managed.tf new file mode 100644 index 000000000..19bacb034 --- /dev/null +++ b/modules/cloud-run-v2/workerpool-managed.tf @@ -0,0 +1,190 @@ +/** + * 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_cloud_run_v2_worker_pool" "default_managed" { + count = var.type == "WORKERPOOL" && var.managed_revision ? 1 : 0 + provider = google-beta + project = local.project_id + location = local.location + name = var.name + labels = var.labels + launch_stage = var.launch_stage + deletion_protection = var.deletion_protection + + dynamic "scaling" { + for_each = var.workerpool_config.scaling == null ? [] : [""] + content { + scaling_mode = var.workerpool_config.scaling.mode + max_instance_count = var.workerpool_config.scaling.max_instance_count + min_instance_count = var.workerpool_config.scaling.min_instance_count + manual_instance_count = var.workerpool_config.scaling.manual_instance_count + } + } + + template { + labels = var.revision.labels + encryption_key = var.encryption_key + revision = local.revision_name + + gpu_zonal_redundancy_disabled = var.revision.gpu_zonal_redundancy_disabled + dynamic "node_selector" { + for_each = var.revision.node_selector == null ? [] : [""] + content { + accelerator = var.revision.node_selector.accelerator + } + } + # Serverless VPC connector is not supported + # dynamic "vpc_access" { + # for_each = local.connector == null ? [] : [""] + # content { + # connector = local.connector + # egress = try(var.revision.vpc_access.egress, null) + # } + # } + dynamic "vpc_access" { + for_each = var.revision.vpc_access.subnet == null && var.revision.vpc_access.network == null ? [] : [""] + content { + egress = var.revision.vpc_access.egress + network_interfaces { + subnetwork = var.revision.vpc_access.subnet == null ? null : lookup( + local.ctx.subnets, var.revision.vpc_access.subnet, + var.revision.vpc_access.subnet + ) + network = var.revision.vpc_access.network == null ? null : lookup( + local.ctx.networks, var.revision.vpc_access.network, + var.revision.vpc_access.network + ) + tags = var.revision.vpc_access.tags + } + } + } + service_account = local.service_account_email + dynamic "containers" { + for_each = var.containers + content { + name = containers.key + image = containers.value.image + command = containers.value.command + args = containers.value.args + dynamic "env" { + for_each = coalesce(containers.value.env, tomap({})) + content { + name = env.key + value = env.value + } + } + dynamic "env" { + for_each = coalesce(containers.value.env_from_key, tomap({})) + content { + name = env.key + value_source { + secret_key_ref { + secret = env.value.secret + version = env.value.version + } + } + } + } + dynamic "resources" { + for_each = containers.value.resources == null ? [] : [""] + content { + limits = containers.value.resources.limits + } + } + dynamic "volume_mounts" { + for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k != "cloudsql" } + content { + name = volume_mounts.key + mount_path = volume_mounts.value + } + } + # CloudSQL is the last mount in the list returned by API + dynamic "volume_mounts" { + for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k == "cloudsql" } + content { + name = volume_mounts.key + mount_path = volume_mounts.value + } + } + } + } + dynamic "volumes" { + for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances == null } + content { + name = volumes.key + dynamic "secret" { + for_each = volumes.value.secret == null ? [] : [""] + content { + secret = volumes.value.secret.name + default_mode = volumes.value.secret.default_mode + dynamic "items" { + for_each = volumes.value.secret.path == null ? [] : [""] + content { + path = volumes.value.secret.path + version = volumes.value.secret.version + mode = volumes.value.secret.mode + } + } + } + } + + dynamic "empty_dir" { + for_each = volumes.value.empty_dir_size == null ? [] : [""] + content { + medium = "MEMORY" + size_limit = volumes.value.empty_dir_size + } + } + dynamic "gcs" { + for_each = volumes.value.gcs == null ? [] : [""] + content { + bucket = volumes.value.gcs.bucket + read_only = volumes.value.gcs.is_read_only + } + } + dynamic "nfs" { + for_each = volumes.value.nfs == null ? [] : [""] + content { + server = volumes.value.nfs.server + path = volumes.value.nfs.path + read_only = volumes.value.nfs.is_read_only + } + } + } + } + # CloudSQL is the last volume in the list returned by API + dynamic "volumes" { + for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances != null } + content { + name = volumes.key + dynamic "cloud_sql_instance" { + for_each = length(coalesce(volumes.value.cloud_sql_instances, [])) == 0 ? [] : [""] + content { + instances = volumes.value.cloud_sql_instances + } + } + } + } + } + + lifecycle { + ignore_changes = [ + client, + client_version, + template[0].annotations["run.googleapis.com/operation-id"], + ] + } +} diff --git a/modules/cloud-run-v2/workerpool-unmanaged.tf b/modules/cloud-run-v2/workerpool-unmanaged.tf new file mode 100644 index 000000000..5f05eca58 --- /dev/null +++ b/modules/cloud-run-v2/workerpool-unmanaged.tf @@ -0,0 +1,193 @@ +/** + * 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_cloud_run_v2_worker_pool" "default_unmanaged" { + count = var.type == "WORKERPOOL" && !var.managed_revision ? 1 : 0 + provider = google-beta + project = local.project_id + location = local.location + name = var.name + labels = var.labels + launch_stage = var.launch_stage + deletion_protection = var.deletion_protection + + dynamic "scaling" { + for_each = var.workerpool_config.scaling == null ? [] : [""] + content { + scaling_mode = var.workerpool_config.scaling.mode + max_instance_count = var.workerpool_config.scaling.max_instance_count + min_instance_count = var.workerpool_config.scaling.min_instance_count + manual_instance_count = var.workerpool_config.scaling.manual_instance_count + } + } + + template { + labels = var.revision.labels + encryption_key = var.encryption_key + revision = local.revision_name + + gpu_zonal_redundancy_disabled = var.revision.gpu_zonal_redundancy_disabled + dynamic "node_selector" { + for_each = var.revision.node_selector == null ? [] : [""] + content { + accelerator = var.revision.node_selector.accelerator + } + } + + # Serverless VPC connector is not supported + # dynamic "vpc_access" { + # for_each = local.connector == null ? [] : [""] + # content { + # connector = local.connector + # egress = try(var.revision.vpc_access.egress, null) + # } + # } + dynamic "vpc_access" { + for_each = var.revision.vpc_access.subnet == null && var.revision.vpc_access.network == null ? [] : [""] + content { + egress = var.revision.vpc_access.egress + network_interfaces { + subnetwork = var.revision.vpc_access.subnet == null ? null : lookup( + local.ctx.subnets, var.revision.vpc_access.subnet, + var.revision.vpc_access.subnet + ) + network = var.revision.vpc_access.network == null ? null : lookup( + local.ctx.networks, var.revision.vpc_access.network, + var.revision.vpc_access.network + ) + tags = var.revision.vpc_access.tags + } + } + } + service_account = local.service_account_email + dynamic "containers" { + for_each = var.containers + content { + name = containers.key + image = containers.value.image + command = containers.value.command + args = containers.value.args + dynamic "env" { + for_each = coalesce(containers.value.env, tomap({})) + content { + name = env.key + value = env.value + } + } + dynamic "env" { + for_each = coalesce(containers.value.env_from_key, tomap({})) + content { + name = env.key + value_source { + secret_key_ref { + secret = env.value.secret + version = env.value.version + } + } + } + } + dynamic "resources" { + for_each = containers.value.resources == null ? [] : [""] + content { + limits = containers.value.resources.limits + } + } + dynamic "volume_mounts" { + for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k != "cloudsql" } + content { + name = volume_mounts.key + mount_path = volume_mounts.value + } + } + # CloudSQL is the last mount in the list returned by API + dynamic "volume_mounts" { + for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k == "cloudsql" } + content { + name = volume_mounts.key + mount_path = volume_mounts.value + } + } + } + } + dynamic "volumes" { + for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances == null } + content { + name = volumes.key + dynamic "secret" { + for_each = volumes.value.secret == null ? [] : [""] + content { + secret = volumes.value.secret.name + default_mode = volumes.value.secret.default_mode + dynamic "items" { + for_each = volumes.value.secret.path == null ? [] : [""] + content { + path = volumes.value.secret.path + version = volumes.value.secret.version + mode = volumes.value.secret.mode + } + } + } + } + + dynamic "empty_dir" { + for_each = volumes.value.empty_dir_size == null ? [] : [""] + content { + medium = "MEMORY" + size_limit = volumes.value.empty_dir_size + } + } + dynamic "gcs" { + for_each = volumes.value.gcs == null ? [] : [""] + content { + bucket = volumes.value.gcs.bucket + read_only = volumes.value.gcs.is_read_only + } + } + dynamic "nfs" { + for_each = volumes.value.nfs == null ? [] : [""] + content { + server = volumes.value.nfs.server + path = volumes.value.nfs.path + read_only = volumes.value.nfs.is_read_only + } + } + } + } + # CloudSQL is the last volume in the list returned by API + dynamic "volumes" { + for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances != null } + content { + name = volumes.key + dynamic "cloud_sql_instance" { + for_each = length(coalesce(volumes.value.cloud_sql_instances, [])) == 0 ? [] : [""] + content { + instances = volumes.value.cloud_sql_instances + } + } + } + } + } + + lifecycle { + ignore_changes = [ + client, + client_version, + template[0].annotations["run.googleapis.com/operation-id"], + template[0].containers, + template[0].labels + ] + } +} diff --git a/modules/cloud-run-v2/workerpool.tf b/modules/cloud-run-v2/workerpool.tf index 121305640..22f85feee 100644 --- a/modules/cloud-run-v2/workerpool.tf +++ b/modules/cloud-run-v2/workerpool.tf @@ -14,351 +14,11 @@ * limitations under the License. */ -resource "google_cloud_run_v2_worker_pool" "default_managed" { - count = var.type == "WORKERPOOL" && var.managed_revision ? 1 : 0 - provider = google-beta - project = var.project_id - location = var.region - name = var.name - labels = var.labels - launch_stage = var.launch_stage - deletion_protection = var.deletion_protection - - dynamic "scaling" { - for_each = var.workerpool_config.scaling == null ? [] : [""] - content { - scaling_mode = var.workerpool_config.scaling.mode - max_instance_count = var.workerpool_config.scaling.max_instance_count - min_instance_count = var.workerpool_config.scaling.min_instance_count - manual_instance_count = var.workerpool_config.scaling.manual_instance_count - } - } - - template { - labels = var.revision.labels - encryption_key = var.encryption_key - revision = local.revision_name - - gpu_zonal_redundancy_disabled = var.revision.gpu_zonal_redundancy_disabled - dynamic "node_selector" { - for_each = var.revision.node_selector == null ? [] : [""] - content { - accelerator = var.revision.node_selector.accelerator - } - } - # Serverless VPC connector is not supported - # dynamic "vpc_access" { - # for_each = local.connector == null ? [] : [""] - # content { - # connector = local.connector - # egress = try(var.revision.vpc_access.egress, null) - # } - # } - dynamic "vpc_access" { - for_each = var.revision.vpc_access.subnet == null && var.revision.vpc_access.network == null ? [] : [""] - content { - egress = var.revision.vpc_access.egress - network_interfaces { - subnetwork = var.revision.vpc_access.subnet - network = var.revision.vpc_access.network - tags = var.revision.vpc_access.tags - } - } - } - service_account = local.service_account_email - dynamic "containers" { - for_each = var.containers - content { - name = containers.key - image = containers.value.image - command = containers.value.command - args = containers.value.args - dynamic "env" { - for_each = coalesce(containers.value.env, tomap({})) - content { - name = env.key - value = env.value - } - } - dynamic "env" { - for_each = coalesce(containers.value.env_from_key, tomap({})) - content { - name = env.key - value_source { - secret_key_ref { - secret = env.value.secret - version = env.value.version - } - } - } - } - dynamic "resources" { - for_each = containers.value.resources == null ? [] : [""] - content { - limits = containers.value.resources.limits - } - } - dynamic "volume_mounts" { - for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k != "cloudsql" } - content { - name = volume_mounts.key - mount_path = volume_mounts.value - } - } - # CloudSQL is the last mount in the list returned by API - dynamic "volume_mounts" { - for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k == "cloudsql" } - content { - name = volume_mounts.key - mount_path = volume_mounts.value - } - } - } - } - dynamic "volumes" { - for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances == null } - content { - name = volumes.key - dynamic "secret" { - for_each = volumes.value.secret == null ? [] : [""] - content { - secret = volumes.value.secret.name - default_mode = volumes.value.secret.default_mode - dynamic "items" { - for_each = volumes.value.secret.path == null ? [] : [""] - content { - path = volumes.value.secret.path - version = volumes.value.secret.version - mode = volumes.value.secret.mode - } - } - } - } - - dynamic "empty_dir" { - for_each = volumes.value.empty_dir_size == null ? [] : [""] - content { - medium = "MEMORY" - size_limit = volumes.value.empty_dir_size - } - } - dynamic "gcs" { - for_each = volumes.value.gcs == null ? [] : [""] - content { - bucket = volumes.value.gcs.bucket - read_only = volumes.value.gcs.is_read_only - } - } - dynamic "nfs" { - for_each = volumes.value.nfs == null ? [] : [""] - content { - server = volumes.value.nfs.server - path = volumes.value.nfs.path - read_only = volumes.value.nfs.is_read_only - } - } - } - } - # CloudSQL is the last volume in the list returned by API - dynamic "volumes" { - for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances != null } - content { - name = volumes.key - dynamic "cloud_sql_instance" { - for_each = length(coalesce(volumes.value.cloud_sql_instances, [])) == 0 ? [] : [""] - content { - instances = volumes.value.cloud_sql_instances - } - } - } - } - } - - lifecycle { - ignore_changes = [ - client, - client_version, - template[0].annotations["run.googleapis.com/operation-id"], - ] - } -} - -resource "google_cloud_run_v2_worker_pool" "default_unmanaged" { - count = var.type == "WORKERPOOL" && !var.managed_revision ? 1 : 0 - provider = google-beta - project = var.project_id - location = var.region - name = var.name - labels = var.labels - launch_stage = var.launch_stage - deletion_protection = var.deletion_protection - - dynamic "scaling" { - for_each = var.workerpool_config.scaling == null ? [] : [""] - content { - scaling_mode = var.workerpool_config.scaling.mode - max_instance_count = var.workerpool_config.scaling.max_instance_count - min_instance_count = var.workerpool_config.scaling.min_instance_count - manual_instance_count = var.workerpool_config.scaling.manual_instance_count - } - } - - template { - labels = var.revision.labels - encryption_key = var.encryption_key - revision = local.revision_name - - gpu_zonal_redundancy_disabled = var.revision.gpu_zonal_redundancy_disabled - dynamic "node_selector" { - for_each = var.revision.node_selector == null ? [] : [""] - content { - accelerator = var.revision.node_selector.accelerator - } - } - - # Serverless VPC connector is not supported - # dynamic "vpc_access" { - # for_each = local.connector == null ? [] : [""] - # content { - # connector = local.connector - # egress = try(var.revision.vpc_access.egress, null) - # } - # } - dynamic "vpc_access" { - for_each = var.revision.vpc_access.subnet == null && var.revision.vpc_access.network == null ? [] : [""] - content { - egress = var.revision.vpc_access.egress - network_interfaces { - subnetwork = var.revision.vpc_access.subnet - network = var.revision.vpc_access.network - tags = var.revision.vpc_access.tags - } - } - } - service_account = local.service_account_email - dynamic "containers" { - for_each = var.containers - content { - name = containers.key - image = containers.value.image - command = containers.value.command - args = containers.value.args - dynamic "env" { - for_each = coalesce(containers.value.env, tomap({})) - content { - name = env.key - value = env.value - } - } - dynamic "env" { - for_each = coalesce(containers.value.env_from_key, tomap({})) - content { - name = env.key - value_source { - secret_key_ref { - secret = env.value.secret - version = env.value.version - } - } - } - } - dynamic "resources" { - for_each = containers.value.resources == null ? [] : [""] - content { - limits = containers.value.resources.limits - } - } - dynamic "volume_mounts" { - for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k != "cloudsql" } - content { - name = volume_mounts.key - mount_path = volume_mounts.value - } - } - # CloudSQL is the last mount in the list returned by API - dynamic "volume_mounts" { - for_each = { for k, v in coalesce(containers.value.volume_mounts, tomap({})) : k => v if k == "cloudsql" } - content { - name = volume_mounts.key - mount_path = volume_mounts.value - } - } - } - } - dynamic "volumes" { - for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances == null } - content { - name = volumes.key - dynamic "secret" { - for_each = volumes.value.secret == null ? [] : [""] - content { - secret = volumes.value.secret.name - default_mode = volumes.value.secret.default_mode - dynamic "items" { - for_each = volumes.value.secret.path == null ? [] : [""] - content { - path = volumes.value.secret.path - version = volumes.value.secret.version - mode = volumes.value.secret.mode - } - } - } - } - - dynamic "empty_dir" { - for_each = volumes.value.empty_dir_size == null ? [] : [""] - content { - medium = "MEMORY" - size_limit = volumes.value.empty_dir_size - } - } - dynamic "gcs" { - for_each = volumes.value.gcs == null ? [] : [""] - content { - bucket = volumes.value.gcs.bucket - read_only = volumes.value.gcs.is_read_only - } - } - dynamic "nfs" { - for_each = volumes.value.nfs == null ? [] : [""] - content { - server = volumes.value.nfs.server - path = volumes.value.nfs.path - read_only = volumes.value.nfs.is_read_only - } - } - } - } - # CloudSQL is the last volume in the list returned by API - dynamic "volumes" { - for_each = { for k, v in var.volumes : k => v if v.cloud_sql_instances != null } - content { - name = volumes.key - dynamic "cloud_sql_instance" { - for_each = length(coalesce(volumes.value.cloud_sql_instances, [])) == 0 ? [] : [""] - content { - instances = volumes.value.cloud_sql_instances - } - } - } - } - } - lifecycle { - ignore_changes = [ - client, - client_version, - template[0].annotations["run.googleapis.com/operation-id"], - template[0].containers, - template[0].labels - ] - } -} - resource "google_cloud_run_v2_worker_pool_iam_binding" "binding" { for_each = var.type == "WORKERPOOL" ? var.iam : {} project = local.resource.project location = local.resource.location name = local.resource.name - role = each.key - members = each.value + role = lookup(local.ctx.custom_roles, each.key, each.key) + members = [for member in each.value : lookup(local.ctx.iam_principals, member, member)] } diff --git a/modules/net-lb-app-ext/recipe-cloud-run-iap/README.md b/modules/net-lb-app-ext/recipe-cloud-run-iap/README.md index 19e99f2c7..13fc06d72 100644 --- a/modules/net-lb-app-ext/recipe-cloud-run-iap/README.md +++ b/modules/net-lb-app-ext/recipe-cloud-run-iap/README.md @@ -64,5 +64,5 @@ module "test" { "group:mygroup3@myorg.com" ] } -# tftest modules=6 resources=24 +# tftest modules=6 resources=26 ``` diff --git a/modules/net-lb-app-ext/recipe-cloud-run-iap/main.tf b/modules/net-lb-app-ext/recipe-cloud-run-iap/main.tf index a6ba4e3f9..2f4eb8d37 100644 --- a/modules/net-lb-app-ext/recipe-cloud-run-iap/main.tf +++ b/modules/net-lb-app-ext/recipe-cloud-run-iap/main.tf @@ -68,8 +68,7 @@ module "backend_service" { module.project.service_agents.iap.iam_email ] } - deletion_protection = false - service_account_create = true + deletion_protection = false } module "addresses" { diff --git a/tests/modules/cloud_function_v1/context-subnet-project.tfvars b/tests/modules/cloud_function_v1/context-subnet-project.tfvars index 6428c97e1..937f899f5 100644 --- a/tests/modules/cloud_function_v1/context-subnet-project.tfvars +++ b/tests/modules/cloud_function_v1/context-subnet-project.tfvars @@ -43,8 +43,7 @@ service_account_config = { ] } vpc_connector = { - create = true - name = "connector_name" + name = "connector_name" } vpc_connector_create = { instances = { diff --git a/tests/modules/cloud_function_v1/context-subnet.tfvars b/tests/modules/cloud_function_v1/context-subnet.tfvars index 7512e3ab3..0fbe0ee6c 100644 --- a/tests/modules/cloud_function_v1/context-subnet.tfvars +++ b/tests/modules/cloud_function_v1/context-subnet.tfvars @@ -43,8 +43,7 @@ service_account_config = { ] } vpc_connector = { - create = true - name = "connector_name" + name = "connector_name" } vpc_connector_create = { instances = { diff --git a/tests/modules/cloud_function_v1/context.tfvars b/tests/modules/cloud_function_v1/context.tfvars index f6eb5e3e3..9f937b04a 100644 --- a/tests/modules/cloud_function_v1/context.tfvars +++ b/tests/modules/cloud_function_v1/context.tfvars @@ -43,8 +43,7 @@ service_account_config = { ] } vpc_connector = { - create = true - name = "connector_name" + name = "connector_name" } vpc_connector_create = { ip_cidr_range = "$cidr_ranges:test" diff --git a/tests/modules/cloud_function_v2/context-subnet-project.tfvars b/tests/modules/cloud_function_v2/context-subnet-project.tfvars index 6428c97e1..937f899f5 100644 --- a/tests/modules/cloud_function_v2/context-subnet-project.tfvars +++ b/tests/modules/cloud_function_v2/context-subnet-project.tfvars @@ -43,8 +43,7 @@ service_account_config = { ] } vpc_connector = { - create = true - name = "connector_name" + name = "connector_name" } vpc_connector_create = { instances = { diff --git a/tests/modules/cloud_function_v2/context-subnet.tfvars b/tests/modules/cloud_function_v2/context-subnet.tfvars index 7512e3ab3..0fbe0ee6c 100644 --- a/tests/modules/cloud_function_v2/context-subnet.tfvars +++ b/tests/modules/cloud_function_v2/context-subnet.tfvars @@ -43,8 +43,7 @@ service_account_config = { ] } vpc_connector = { - create = true - name = "connector_name" + name = "connector_name" } vpc_connector_create = { instances = { diff --git a/tests/modules/cloud_function_v2/context.tfvars b/tests/modules/cloud_function_v2/context.tfvars index f6eb5e3e3..9f937b04a 100644 --- a/tests/modules/cloud_function_v2/context.tfvars +++ b/tests/modules/cloud_function_v2/context.tfvars @@ -43,8 +43,7 @@ service_account_config = { ] } vpc_connector = { - create = true - name = "connector_name" + name = "connector_name" } vpc_connector_create = { ip_cidr_range = "$cidr_ranges:test" diff --git a/tests/modules/cloud_run_v2/context-subnet-project.tfvars b/tests/modules/cloud_run_v2/context-subnet-project.tfvars new file mode 100644 index 000000000..3fe9e76ca --- /dev/null +++ b/tests/modules/cloud_run_v2/context-subnet-project.tfvars @@ -0,0 +1,60 @@ +name = "test-cf-kms" +bucket_name = "bucket" +bundle_config = { + path = "gs://assets/sample-function.zip" +} +context = { + cidr_ranges = { + test = "10.10.20.0/28" + } + custom_roles = { + myrole_one = "organizations/366118655033/roles/myRoleOne" + } + iam_principals = { + mygroup = "group:test-group@example.com" + } + kms_keys = { + test = "projects/foo-prod-sec-core/locations/global/keyRings/prod-global-default/cryptoKeys/compute" + } + locations = { + ew8 = "europe-west8" + } + networks = { + test = "projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0" + } + project_ids = { + test = "foo-test-0" + } + subnets = { + test = "projects/foo-dev-net-spoke-0/regions/europe-west1/subnetworks/gce" + } +} +kms_key = "$kms_keys:test" +iam = { + "$custom_roles:myrole_one" = [ + "$iam_principals:mygroup" + ] +} +project_id = "$project_ids:test" +region = "$locations:ew8" +service_account_config = { + roles = [ + "$custom_roles:myrole_one" + ] +} +revision = { + vpc_access = { + egress_settings = "ALL_TRAFFIC" + } +} + +vpc_connector_create = { + instances = { + max = 10 + min = 3 + } + subnet = { + name = "$subnets:test" + project_id = "$project_ids:test" + } +} diff --git a/tests/modules/cloud_run_v2/context-subnet-project.yaml b/tests/modules/cloud_run_v2/context-subnet-project.yaml new file mode 100644 index 000000000..6ba64800a --- /dev/null +++ b/tests/modules/cloud_run_v2/context-subnet-project.yaml @@ -0,0 +1,88 @@ +# 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. + +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_protection: true + description: null + effective_labels: + goog-terraform-provisioned: 'true' + iap_enabled: false + invoker_iam_disabled: false + labels: null + location: europe-west8 + multi_region_settings: [] + name: test-cf-kms + project: foo-test-0 + scaling: [] + template: + - annotations: null + containers: [] + encryption_key: null + execution_environment: EXECUTION_ENVIRONMENT_GEN1 + gpu_zonal_redundancy_disabled: null + labels: null + node_selector: [] + revision: null + service_account: test-cf-kms@foo-test-0.iam.gserviceaccount.com + service_mesh: [] + session_affinity: null + volumes: [] + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + google_cloud_run_v2_service_iam_binding.binding["$custom_roles:myrole_one"]: + condition: [] + members: + - group:test-group@example.com + role: organizations/366118655033/roles/myRoleOne + google_project_iam_member.default["organizations/366118655033/roles/myRoleOne"]: + condition: [] + member: serviceAccount:test-cf-kms@foo-test-0.iam.gserviceaccount.com + project: foo-test-0 + role: organizations/366118655033/roles/myRoleOne + google_service_account.service_account[0]: + account_id: test-cf-kms + create_ignore_already_exists: null + description: null + disabled: false + display_name: test-cf-kms + email: test-cf-kms@foo-test-0.iam.gserviceaccount.com + member: serviceAccount:test-cf-kms@foo-test-0.iam.gserviceaccount.com + project: foo-test-0 + timeouts: null + google_vpc_access_connector.connector[0]: + ip_cidr_range: null + machine_type: e2-micro + max_instances: 10 + min_instances: 3 + name: test-cf-kms + project: foo-test-0 + region: europe-west8 + subnet: + - name: gce + project_id: foo-test-0 + timeouts: null + +counts: + google_cloud_run_v2_service: 1 + google_cloud_run_v2_service_iam_binding: 1 + google_vpc_access_connector: 1 diff --git a/tests/modules/cloud_run_v2/context-subnet.tfvars b/tests/modules/cloud_run_v2/context-subnet.tfvars new file mode 100644 index 000000000..e32a42b0e --- /dev/null +++ b/tests/modules/cloud_run_v2/context-subnet.tfvars @@ -0,0 +1,58 @@ +name = "test-cf-kms" +bucket_name = "bucket" +bundle_config = { + path = "gs://assets/sample-function.zip" +} +context = { + cidr_ranges = { + test = "10.10.20.0/28" + } + custom_roles = { + myrole_one = "organizations/366118655033/roles/myRoleOne" + } + iam_principals = { + mygroup = "group:test-group@example.com" + } + kms_keys = { + test = "projects/foo-prod-sec-core/locations/global/keyRings/prod-global-default/cryptoKeys/compute" + } + locations = { + ew8 = "europe-west8" + } + networks = { + test = "projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0" + } + project_ids = { + test = "foo-test-0" + } + subnets = { + test = "projects/foo-dev-net-spoke-0/regions/europe-west1/subnetworks/gce" + } +} +kms_key = "$kms_keys:test" +iam = { + "$custom_roles:myrole_one" = [ + "$iam_principals:mygroup" + ] +} +project_id = "$project_ids:test" +region = "$locations:ew8" +service_account_config = { + roles = [ + "$custom_roles:myrole_one" + ] +} +revision = { + vpc_access = { + egress_settings = "ALL_TRAFFIC" + } +} +vpc_connector_create = { + instances = { + max = 10 + min = 3 + } + subnet = { + name = "$subnets:test" + } +} diff --git a/tests/modules/cloud_run_v2/context-subnet.yaml b/tests/modules/cloud_run_v2/context-subnet.yaml new file mode 100644 index 000000000..85449a176 --- /dev/null +++ b/tests/modules/cloud_run_v2/context-subnet.yaml @@ -0,0 +1,78 @@ +# 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. + +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_protection: true + description: null + effective_labels: + goog-terraform-provisioned: 'true' + iap_enabled: false + invoker_iam_disabled: false + labels: null + location: europe-west8 + multi_region_settings: [] + name: test-cf-kms + project: foo-test-0 + scaling: [] + template: + - annotations: null + containers: [] + encryption_key: null + execution_environment: EXECUTION_ENVIRONMENT_GEN1 + gpu_zonal_redundancy_disabled: null + labels: null + node_selector: [] + revision: null + service_account: test-cf-kms@foo-test-0.iam.gserviceaccount.com + service_mesh: [] + session_affinity: null + volumes: [] + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + google_cloud_run_v2_service_iam_binding.binding["$custom_roles:myrole_one"]: + condition: [] + members: + - group:test-group@example.com + role: organizations/366118655033/roles/myRoleOne + google_project_iam_member.default["organizations/366118655033/roles/myRoleOne"]: + condition: [] + member: serviceAccount:test-cf-kms@foo-test-0.iam.gserviceaccount.com + project: foo-test-0 + role: organizations/366118655033/roles/myRoleOne + google_vpc_access_connector.connector[0]: + ip_cidr_range: null + machine_type: e2-micro + max_instances: 10 + min_instances: 3 + name: test-cf-kms + project: foo-test-0 + region: europe-west8 + subnet: + - name: gce + project_id: foo-dev-net-spoke-0 + timeouts: null + +counts: + google_cloud_run_v2_service: 1 + google_cloud_run_v2_service_iam_binding: 1 + google_vpc_access_connector: 1 diff --git a/tests/modules/cloud_run_v2/context.tfvars b/tests/modules/cloud_run_v2/context.tfvars new file mode 100644 index 000000000..16d5bb097 --- /dev/null +++ b/tests/modules/cloud_run_v2/context.tfvars @@ -0,0 +1,54 @@ +name = "test-run-context" +context = { + cidr_ranges = { + test = "10.10.20.0/28" + } + custom_roles = { + myrole_one = "organizations/366118655033/roles/myRoleOne" + } + iam_principals = { + mygroup = "group:test-group@example.com" + } + kms_keys = { + test = "projects/foo-prod-sec-core/locations/global/keyRings/prod-global-default/cryptoKeys/compute" + } + locations = { + ew8 = "europe-west8" + } + networks = { + test = "projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0" + } + project_ids = { + test = "foo-test-0" + } + subnets = { + test = "projects/foo-dev-net-spoke-0/regions/europe-west1/subnetworks/gce" + } +} +kms_key = "$kms_keys:test" +iam = { + "$custom_roles:myrole_one" = [ + "$iam_principals:mygroup" + ] +} +project_id = "$project_ids:test" +region = "$locations:ew8" +service_account_config = { + roles = [ + "$custom_roles:myrole_one" + ] +} +revision = { + vpc_access = { + egress_settings = "ALL_TRAFFIC" + } +} +vpc_connector_create = { + ip_cidr_range = "$cidr_ranges:test" + name = "connector_name" + network = "$networks:test" + instances = { + max = 10 + min = 3 + } +} diff --git a/tests/modules/cloud_run_v2/context.yaml b/tests/modules/cloud_run_v2/context.yaml new file mode 100644 index 000000000..cb82fd436 --- /dev/null +++ b/tests/modules/cloud_run_v2/context.yaml @@ -0,0 +1,87 @@ +# 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. + +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_protection: true + description: null + effective_labels: + goog-terraform-provisioned: 'true' + iap_enabled: false + invoker_iam_disabled: false + labels: null + location: europe-west8 + multi_region_settings: [] + name: test-run-context + project: foo-test-0 + scaling: [] + template: + - annotations: null + containers: [] + encryption_key: null + execution_environment: EXECUTION_ENVIRONMENT_GEN1 + gpu_zonal_redundancy_disabled: null + labels: null + node_selector: [] + revision: null + service_account: test-run-context@foo-test-0.iam.gserviceaccount.com + service_mesh: [] + session_affinity: null + volumes: [] + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + google_cloud_run_v2_service_iam_binding.binding["$custom_roles:myrole_one"]: + condition: [] + members: + - group:test-group@example.com + role: organizations/366118655033/roles/myRoleOne + google_project_iam_member.default["organizations/366118655033/roles/myRoleOne"]: + condition: [] + member: serviceAccount:test-run-context@foo-test-0.iam.gserviceaccount.com + project: foo-test-0 + role: organizations/366118655033/roles/myRoleOne + google_service_account.service_account[0]: + account_id: test-run-context + create_ignore_already_exists: null + description: null + disabled: false + display_name: test-run-context + email: test-run-context@foo-test-0.iam.gserviceaccount.com + member: serviceAccount:test-run-context@foo-test-0.iam.gserviceaccount.com + project: foo-test-0 + timeouts: null + google_vpc_access_connector.connector[0]: + ip_cidr_range: 10.10.20.0/28 + machine_type: e2-micro + max_instances: 10 + min_instances: 3 + name: connector_name + network: projects/foo-dev-net-spoke-0/global/networks/dev-spoke-0 + project: foo-test-0 + region: europe-west8 + subnet: [] + timeouts: null + +counts: + google_cloud_run_v2_service: 1 + google_cloud_run_v2_service_iam_binding: 1 + google_vpc_access_connector: 1 diff --git a/tests/modules/cloud_run_v2/examples/cloudsql.yaml b/tests/modules/cloud_run_v2/examples/cloudsql.yaml index d34e887a8..548b31bd3 100644 --- a/tests/modules/cloud_run_v2/examples/cloudsql.yaml +++ b/tests/modules/cloud_run_v2/examples/cloudsql.yaml @@ -15,7 +15,7 @@ values: module.cloud_run.google_cloud_run_v2_service.service[0]: location: europe-west8 - name: hello + name: example-hello project: project-id template: - containers: diff --git a/tests/modules/cloud_run_v2/examples/cmek.yaml b/tests/modules/cloud_run_v2/examples/cmek.yaml new file mode 100644 index 000000000..dd39aacf2 --- /dev/null +++ b/tests/modules/cloud_run_v2/examples/cmek.yaml @@ -0,0 +1,90 @@ +# 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. + +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_protection: false + description: null + effective_labels: + goog-terraform-provisioned: 'true' + iap_enabled: false + invoker_iam_disabled: false + labels: null + location: europe-west8 + multi_region_settings: [] + name: example-hello + project: test-cloudrun + scaling: [] + 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 + volume_mounts: [] + working_dir: null + execution_environment: EXECUTION_ENVIRONMENT_GEN1 + gpu_zonal_redundancy_disabled: null + labels: null + node_selector: [] + revision: null + service_account: example-hello@test-cloudrun.iam.gserviceaccount.com + service_mesh: [] + session_affinity: null + volumes: [] + vpc_access: [] + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + module.kms.google_kms_crypto_key.default["key-regional"]: + effective_labels: + goog-terraform-provisioned: 'true' + labels: null + name: key-regional + purpose: ENCRYPT_DECRYPT + rotation_period: null + skip_initial_version_creation: false + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + module.kms.google_kms_key_ring.default[0]: + location: europe-west8 + name: test-keyring + project: test-cloudrun + timeouts: null + module.kms.google_kms_key_ring_iam_binding.authoritative["roles/cloudkms.cryptoKeyEncrypterDecrypter"]: + condition: [] + role: roles/cloudkms.cryptoKeyEncrypterDecrypter + module.project.google_project.project[0]: + + name: test-cloudrun + +counts: + google_cloud_run_v2_service: 1 + google_kms_crypto_key: 1 + google_kms_key_ring: 1 + google_kms_key_ring_iam_binding: 1 + google_project: 1 diff --git a/tests/modules/cloud_run_v2/examples/gcs-mount.yaml b/tests/modules/cloud_run_v2/examples/gcs-mount.yaml index ee462e4b6..b2ef34d9a 100644 --- a/tests/modules/cloud_run_v2/examples/gcs-mount.yaml +++ b/tests/modules/cloud_run_v2/examples/gcs-mount.yaml @@ -15,7 +15,7 @@ values: module.cloud_run.google_cloud_run_v2_service.service[0]: location: europe-west8 - name: hello + name: example-hello project: project-id template: - containers: diff --git a/tests/modules/cloud_run_v2/examples/gpu-job.yaml b/tests/modules/cloud_run_v2/examples/gpu-job.yaml index da21bdf32..5624956e6 100644 --- a/tests/modules/cloud_run_v2/examples/gpu-job.yaml +++ b/tests/modules/cloud_run_v2/examples/gpu-job.yaml @@ -23,7 +23,7 @@ values: goog-terraform-provisioned: 'true' labels: null location: europe-west8 - name: job + name: example-job project: project-id run_execution_token: null start_execution_token: null @@ -57,5 +57,3 @@ values: counts: google_cloud_run_v2_job: 1 - modules: 1 - resources: 1 diff --git a/tests/modules/cloud_run_v2/examples/gpu-service.yaml b/tests/modules/cloud_run_v2/examples/gpu-service.yaml index 2467695e1..6ef57b5ab 100644 --- a/tests/modules/cloud_run_v2/examples/gpu-service.yaml +++ b/tests/modules/cloud_run_v2/examples/gpu-service.yaml @@ -67,5 +67,3 @@ values: counts: google_cloud_run_v2_service: 1 - modules: 1 - resources: 1 diff --git a/tests/modules/cloud_run_v2/examples/gpu-workerpool.yaml b/tests/modules/cloud_run_v2/examples/gpu-workerpool.yaml index 68c65492f..7aa710416 100644 --- a/tests/modules/cloud_run_v2/examples/gpu-workerpool.yaml +++ b/tests/modules/cloud_run_v2/examples/gpu-workerpool.yaml @@ -56,5 +56,3 @@ values: counts: google_cloud_run_v2_worker_pool: 1 - modules: 1 - resources: 1 diff --git a/tests/modules/cloud_run_v2/examples/iap.yaml b/tests/modules/cloud_run_v2/examples/iap.yaml new file mode 100644 index 000000000..7940d5861 --- /dev/null +++ b/tests/modules/cloud_run_v2/examples/iap.yaml @@ -0,0 +1,46 @@ +# 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. + +values: + module.cloud_run.google_cloud_run_v2_service.service[0]: + iap_enabled: true + invoker_iam_disabled: false + launch_stage: BETA + location: europe-west8 + name: example-hello + project: project-id + scaling: [] + 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 + volume_mounts: [] + working_dir: null + service_account: example-hello@project-id.iam.gserviceaccount.com + module.cloud_run.google_iap_web_cloud_run_service_iam_binding.binding["1"]: + condition: [] + members: + - group:organization-admins@example.org + role: roles/iap.httpsResourceAccessor + +counts: + google_cloud_run_v2_service: 1 + google_iap_web_cloud_run_service_iam_binding: 1 diff --git a/tests/modules/cloud_run_v2/examples/job-iam-env.yaml b/tests/modules/cloud_run_v2/examples/job-iam-env.yaml index daedaeb9b..097099496 100644 --- a/tests/modules/cloud_run_v2/examples/job-iam-env.yaml +++ b/tests/modules/cloud_run_v2/examples/job-iam-env.yaml @@ -15,7 +15,7 @@ values: module.cloud_run.google_cloud_run_v2_job.job[0]: location: europe-west8 - name: hello + name: example-hello project: project-id template: - template: @@ -46,7 +46,5 @@ values: counts: google_cloud_run_v2_job: 1 google_cloud_run_v2_job_iam_binding: 1 - modules: 1 - resources: 2 outputs: {} diff --git a/tests/modules/cloud_run_v2/examples/service-direct-vpc.yaml b/tests/modules/cloud_run_v2/examples/service-direct-vpc.yaml index 871806b5a..8a90186fa 100644 --- a/tests/modules/cloud_run_v2/examples/service-direct-vpc.yaml +++ b/tests/modules/cloud_run_v2/examples/service-direct-vpc.yaml @@ -15,7 +15,7 @@ values: module.cloud_run.google_cloud_run_v2_service.service[0]: location: europe-west8 - name: hello + name: example-hello project: project-id template: - annotations: null @@ -42,7 +42,5 @@ values: counts: google_cloud_run_v2_service: 1 - modules: 1 - resources: 1 outputs: {} diff --git a/tests/modules/cloud_run_v2/examples/service-eventarc-auditlogs-external-sa.yaml b/tests/modules/cloud_run_v2/examples/service-eventarc-auditlogs-external-sa.yaml index 6eecd6c4b..a99400285 100644 --- a/tests/modules/cloud_run_v2/examples/service-eventarc-auditlogs-external-sa.yaml +++ b/tests/modules/cloud_run_v2/examples/service-eventarc-auditlogs-external-sa.yaml @@ -15,7 +15,7 @@ values: module.cloud_run.google_cloud_run_v2_service.service[0]: location: europe-west8 - name: hello + name: example-hello project: project-id template: - containers: @@ -34,9 +34,6 @@ values: destination: - cloud_run_service: - path: null - region: europe-west8 - service: hello - location: europe-west8 matching_criteria: - attribute: methodName operator: '' @@ -48,15 +45,12 @@ values: operator: '' value: google.cloud.audit.log.v1.written name: audit-log-setiampolicy - project: project-id service_account: fixture-service-account@project-id.iam.gserviceaccount.com counts: google_cloud_run_v2_service: 1 google_cloud_run_v2_service_iam_binding: 1 google_eventarc_trigger: 1 - google_service_account: 1 - modules: 2 - resources: 5 + google_service_account: 2 outputs: {} diff --git a/tests/modules/cloud_run_v2/examples/service-eventarc-pubsub.yaml b/tests/modules/cloud_run_v2/examples/service-eventarc-pubsub.yaml index 125fecbea..af9d6110f 100644 --- a/tests/modules/cloud_run_v2/examples/service-eventarc-pubsub.yaml +++ b/tests/modules/cloud_run_v2/examples/service-eventarc-pubsub.yaml @@ -15,7 +15,7 @@ values: module.cloud_run.google_cloud_run_v2_service.service[0]: location: europe-west8 - name: hello + name: example-hello project: project-id template: - containers: @@ -35,15 +35,11 @@ values: destination: - cloud_run_service: - path: null - region: europe-west8 - service: hello - location: europe-west8 matching_criteria: - attribute: type operator: '' value: google.cloud.pubsub.topic.v1.messagePublished name: pubsub-topic-1 - project: project-id service_account: null transport: - pubsub: @@ -52,7 +48,5 @@ values: counts: google_cloud_run_v2_service: 1 google_eventarc_trigger: 1 - modules: 2 - resources: 4 outputs: {} diff --git a/tests/modules/cloud_run_v2/examples/service-eventarc-storage.yaml b/tests/modules/cloud_run_v2/examples/service-eventarc-storage.yaml index 34c549968..e2b4dd2de 100644 --- a/tests/modules/cloud_run_v2/examples/service-eventarc-storage.yaml +++ b/tests/modules/cloud_run_v2/examples/service-eventarc-storage.yaml @@ -15,7 +15,7 @@ values: module.cloud_run.google_cloud_run_v2_service.service[0]: location: europe-west8 - name: hello + name: example-hello project: project-id template: - containers: @@ -35,9 +35,6 @@ values: destination: - cloud_run_service: - path: /webhook - region: europe-west8 - service: hello - location: europe-west8 matching_criteria: - attribute: bucket operator: '' @@ -46,13 +43,10 @@ values: operator: '' value: google.cloud.storage.object.v1.finalized name: storage-bucket-upload - project: project-id service_account: fixture-service-account@project-id.iam.gserviceaccount.com counts: google_cloud_run_v2_service: 1 google_eventarc_trigger: 1 - modules: 3 - resources: 7 outputs: {} diff --git a/tests/modules/cloud_run_v2/examples/service-external-sa.yaml b/tests/modules/cloud_run_v2/examples/service-external-sa.yaml index a9d65dd42..27567e9bd 100644 --- a/tests/modules/cloud_run_v2/examples/service-external-sa.yaml +++ b/tests/modules/cloud_run_v2/examples/service-external-sa.yaml @@ -15,7 +15,7 @@ values: module.cloud_run.google_cloud_run_v2_service.service[0]: location: europe-west8 - name: hello + name: example-hello project: project-id template: - containers: diff --git a/tests/modules/cloud_run_v2/examples/service-iam-env.yaml b/tests/modules/cloud_run_v2/examples/service-iam-env.yaml index 09c9a2c0a..a6de07f0a 100644 --- a/tests/modules/cloud_run_v2/examples/service-iam-env.yaml +++ b/tests/modules/cloud_run_v2/examples/service-iam-env.yaml @@ -30,7 +30,7 @@ values: invoker_iam_disabled: false labels: null location: europe-west8 - name: hello + name: example-hello project: project-id scaling: [] template: @@ -113,8 +113,8 @@ values: counts: google_cloud_run_v2_service: 1 google_cloud_run_v2_service_iam_binding: 1 + google_project_iam_member: 2 google_secret_manager_secret: 1 google_secret_manager_secret_iam_binding: 1 google_secret_manager_secret_version: 1 - modules: 2 - resources: 5 + google_service_account: 1 diff --git a/tests/modules/cloud_run_v2/examples/service-invoker-iam-disable.yaml b/tests/modules/cloud_run_v2/examples/service-invoker-iam-disable.yaml index 7b1dddffd..fa6dfe4a6 100644 --- a/tests/modules/cloud_run_v2/examples/service-invoker-iam-disable.yaml +++ b/tests/modules/cloud_run_v2/examples/service-invoker-iam-disable.yaml @@ -15,7 +15,7 @@ values: module.cloud_run.google_cloud_run_v2_service.service[0]: location: europe-west8 - name: hello + name: example-hello project: project-id template: - containers: @@ -34,7 +34,5 @@ values: counts: google_cloud_run_v2_service: 1 - modules: 1 - resources: 1 outputs: {} diff --git a/tests/modules/cloud_run_v2/examples/service-otel-sidecar.yaml b/tests/modules/cloud_run_v2/examples/service-otel-sidecar.yaml index f313ec963..0e8029b1e 100644 --- a/tests/modules/cloud_run_v2/examples/service-otel-sidecar.yaml +++ b/tests/modules/cloud_run_v2/examples/service-otel-sidecar.yaml @@ -52,7 +52,5 @@ counts: google_secret_manager_secret: 1 google_secret_manager_secret_iam_binding: 1 google_secret_manager_secret_version: 1 - modules: 2 - resources: 4 outputs: {} diff --git a/tests/modules/cloud_run_v2/examples/service-sa-create.yaml b/tests/modules/cloud_run_v2/examples/service-sa-create.yaml index cdc3699a3..802373962 100644 --- a/tests/modules/cloud_run_v2/examples/service-sa-create.yaml +++ b/tests/modules/cloud_run_v2/examples/service-sa-create.yaml @@ -14,34 +14,81 @@ 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_protection: false + description: null + effective_labels: + goog-terraform-provisioned: 'true' + iap_enabled: false + invoker_iam_disabled: false + labels: null location: europe-west8 - name: hello + multi_region_settings: [] + name: example-hello project: project-id + scaling: [] template: - - containers: - - args: null - command: null - depends_on: null - env: [] - image: us-docker.pkg.dev/cloudrun/container/hello - name: hello - volume_mounts: [] - working_dir: null - execution_environment: EXECUTION_ENVIRONMENT_GEN1 - volumes: [] - vpc_access: [] + - 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 + volume_mounts: [] + working_dir: null + encryption_key: null + execution_environment: EXECUTION_ENVIRONMENT_GEN1 + gpu_zonal_redundancy_disabled: null + labels: null + node_selector: [] + revision: null + service_account: example-hello@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/cloudsql.client"]: + condition: [] + member: serviceAccount:example-hello@project-id.iam.gserviceaccount.com + project: project-id + role: roles/cloudsql.client + module.cloud_run.google_project_iam_member.default["roles/cloudsql.instanceUser"]: + condition: [] + member: serviceAccount:example-hello@project-id.iam.gserviceaccount.com + project: project-id + role: roles/cloudsql.instanceUser + module.cloud_run.google_project_iam_member.default["roles/logging.logWriter"]: + condition: [] + member: serviceAccount:example-hello@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-hello@project-id.iam.gserviceaccount.com + project: project-id + role: roles/monitoring.metricWriter module.cloud_run.google_service_account.service_account[0]: - account_id: tf-cr-hello + account_id: example-hello + create_ignore_already_exists: null description: null disabled: false - display_name: Terraform Cloud Run hello. + display_name: example-hello + email: example-hello@project-id.iam.gserviceaccount.com + member: serviceAccount:example-hello@project-id.iam.gserviceaccount.com project: project-id timeouts: null -counts: - google_cloud_run_v2_service: 1 - google_service_account: 1 - modules: 1 - resources: 2 - outputs: {} diff --git a/tests/modules/cloud_run_v2/examples/service-volume-secretes.yaml b/tests/modules/cloud_run_v2/examples/service-volume-secretes.yaml index cace77eff..ff0fe90ab 100644 --- a/tests/modules/cloud_run_v2/examples/service-volume-secretes.yaml +++ b/tests/modules/cloud_run_v2/examples/service-volume-secretes.yaml @@ -22,7 +22,7 @@ values: description: null labels: null location: europe-west8 - name: hello + name: example-hello project: project-id template: - annotations: null @@ -56,7 +56,6 @@ values: counts: google_cloud_run_v2_service: 1 - modules: 2 - resources: 4 + outputs: {} diff --git a/tests/modules/cloud_run_v2/examples/service-vpc-access-connector-create-sharedvpc.yaml b/tests/modules/cloud_run_v2/examples/service-vpc-access-connector-create-sharedvpc.yaml index 16efec979..38f13a7b6 100644 --- a/tests/modules/cloud_run_v2/examples/service-vpc-access-connector-create-sharedvpc.yaml +++ b/tests/modules/cloud_run_v2/examples/service-vpc-access-connector-create-sharedvpc.yaml @@ -15,7 +15,7 @@ values: module.cloud_run.google_cloud_run_v2_service.service[0]: location: europe-west8 - name: hello + name: example-hello project: test-service template: - containers: @@ -33,7 +33,7 @@ values: machine_type: e2-standard-4 max_throughput: 300 min_throughput: 200 - name: hello + name: example-hello project: test-service region: europe-west8 subnet: @@ -52,7 +52,6 @@ values: counts: google_cloud_run_v2_service: 1 google_vpc_access_connector: 1 - modules: 4 - resources: 59 + outputs: {} diff --git a/tests/modules/cloud_run_v2/examples/service-vpc-access-connector-create.yaml b/tests/modules/cloud_run_v2/examples/service-vpc-access-connector-create.yaml index 5fd4c74e2..96fe2d83d 100644 --- a/tests/modules/cloud_run_v2/examples/service-vpc-access-connector-create.yaml +++ b/tests/modules/cloud_run_v2/examples/service-vpc-access-connector-create.yaml @@ -26,7 +26,7 @@ values: goog-terraform-provisioned: 'true' labels: null location: europe-west8 - name: hello + name: example-hello project: project-id scaling: [] template: @@ -55,7 +55,7 @@ values: machine_type: e2-micro max_instances: 10 min_instances: 3 - name: hello + name: example-hello network: projects/xxx/global/networks/aaa project: project-id region: europe-west8 @@ -65,5 +65,3 @@ values: counts: google_cloud_run_v2_service: 1 google_vpc_access_connector: 1 - modules: 1 - resources: 2 diff --git a/tests/modules/cloud_run_v2/examples/service-vpc-access-connector.yaml b/tests/modules/cloud_run_v2/examples/service-vpc-access-connector.yaml index bee8ea643..2e2398225 100644 --- a/tests/modules/cloud_run_v2/examples/service-vpc-access-connector.yaml +++ b/tests/modules/cloud_run_v2/examples/service-vpc-access-connector.yaml @@ -15,7 +15,7 @@ values: module.cloud_run.google_cloud_run_v2_service.service[0]: location: europe-west9 - name: hello + name: example-hello project: project-id template: - annotations: null @@ -38,7 +38,6 @@ values: counts: google_cloud_run_v2_service: 1 - modules: 1 - resources: 2 + outputs: {} diff --git a/tests/modules/cloud_run_v2/examples/tags.yaml b/tests/modules/cloud_run_v2/examples/tags.yaml index 8948624b6..dbc0631ed 100644 --- a/tests/modules/cloud_run_v2/examples/tags.yaml +++ b/tests/modules/cloud_run_v2/examples/tags.yaml @@ -124,7 +124,5 @@ counts: google_tags_location_tag_binding: 2 google_tags_tag_key: 1 google_tags_tag_value: 3 - modules: 3 - resources: 8 outputs: {} diff --git a/tests/modules/cloud_run_v2/tftest.yaml b/tests/modules/cloud_run_v2/tftest.yaml new file mode 100644 index 000000000..cf724f5f0 --- /dev/null +++ b/tests/modules/cloud_run_v2/tftest.yaml @@ -0,0 +1,20 @@ +# 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. + +module: modules/cloud-run-v2 +tests: + context: + context-subnet: + context-subnet-project: + vpcconnector: diff --git a/tests/modules/cloud_run_v2/vpcconnector.tfvars b/tests/modules/cloud_run_v2/vpcconnector.tfvars new file mode 100644 index 000000000..04d098b38 --- /dev/null +++ b/tests/modules/cloud_run_v2/vpcconnector.tfvars @@ -0,0 +1,9 @@ +project_id = "test-project" +region = "region" +name = "test-run-vpc" +revision = { + vpc_access = { + connector = "projects/test-project/locations/region/connectors/vpc-connector" + egress_settings = "ALL_TRAFFIC" + } +} diff --git a/tests/modules/cloud_run_v2/vpcconnector.yaml b/tests/modules/cloud_run_v2/vpcconnector.yaml new file mode 100644 index 000000000..b87df380c --- /dev/null +++ b/tests/modules/cloud_run_v2/vpcconnector.yaml @@ -0,0 +1,71 @@ +# 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. + +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_protection: true + description: null + effective_labels: + goog-terraform-provisioned: 'true' + iap_enabled: false + invoker_iam_disabled: false + labels: null + location: region + multi_region_settings: [] + name: test-run-vpc + project: test-project + scaling: [] + template: + - annotations: null + containers: [] + encryption_key: null + execution_environment: EXECUTION_ENVIRONMENT_GEN1 + gpu_zonal_redundancy_disabled: null + labels: null + node_selector: [] + revision: null + service_account: test-run-vpc@test-project.iam.gserviceaccount.com + service_mesh: [] + session_affinity: null + volumes: [] + vpc_access: + - connector: projects/test-project/locations/region/connectors/vpc-connector + network_interfaces: [] + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + +counts: + google_cloud_run_v2_service: 1 + +outputs: + id: __missing__ + invoke_command: __missing__ + job: null + resource: __missing__ + resource_name: __missing__ + service: __missing__ + service_account: __missing__ + service_account_email: test-run-vpc@test-project.iam.gserviceaccount.com + service_account_iam_email: serviceAccount:test-run-vpc@test-project.iam.gserviceaccount.com + service_name: __missing__ + service_uri: __missing__ + vpc_connector: null diff --git a/tools/duplicate-diff.py b/tools/duplicate-diff.py index c19af5b1b..86e9659c8 100755 --- a/tools/duplicate-diff.py +++ b/tools/duplicate-diff.py @@ -151,18 +151,22 @@ duplicates = [ [ "modules/cloud-function-v1/serviceaccount.tf", "modules/cloud-function-v2/serviceaccount.tf", + "modules/cloud-run-v2/serviceaccount.tf", ], [ "modules/cloud-function-v1/variables-serviceaccount.tf", "modules/cloud-function-v2/variables-serviceaccount.tf", + "modules/cloud-run-v2/variables-serviceaccount.tf", ], [ "modules/cloud-function-v1/variables-vpcconnector.tf", "modules/cloud-function-v2/variables-vpcconnector.tf", + "modules/cloud-run-v2/variables-vpcconnector.tf", ], [ "modules/cloud-function-v1/vpcconnector.tf", "modules/cloud-function-v2/vpcconnector.tf", + "modules/cloud-run-v2/vpcconnector.tf", ], ]