diff --git a/blueprints/serverless/cloud-run-corporate/main.tf b/blueprints/serverless/cloud-run-corporate/main.tf index 5eb98029c7..d8f568d53f 100644 --- a/blueprints/serverless/cloud-run-corporate/main.tf +++ b/blueprints/serverless/cloud-run-corporate/main.tf @@ -18,11 +18,24 @@ locals { cloud_run_domain = "run.app." service_name_cr1 = "cart" service_name_cr2 = "checkout" - tf_id = (var.tf_identity == null ? null : - length(regexall("iam.gserviceaccount.com", var.tf_identity)) > 0 ? - "serviceAccount:${var.tf_identity}" : "user:${var.tf_identity}") - vpc_sc_create = (length(module.project_prj1) > 0 && - (var.access_policy != null || var.access_policy_create != null)) ? 1 : 0 + tf_id = ( + var.tf_identity == null + ? null + : ( + length(regexall("iam.gserviceaccount.com", var.tf_identity)) > 0 + ? "serviceAccount:${var.tf_identity}" + : "user:${var.tf_identity}" + ) + ) + vpc_sc_create = ( + ( + length(module.project_prj1) > 0 + && + (var.access_policy != null || var.access_policy_create != null) + ) + ? 1 + : 0 + ) } ############################################################################### @@ -109,13 +122,11 @@ module "cloud_run_hello" { project_id = module.project_main.project_id name = "hello" region = var.region - containers = [{ - image = var.image - options = null - ports = null - resources = null - volume_mounts = null - }] + containers = { + default = { + image = var.image + } + } iam = { "roles/run.invoker" = ["allUsers"] } @@ -129,13 +140,11 @@ module "cloud_run_cart" { project_id = module.project_svc1[0].project_id name = local.service_name_cr1 # "cart" region = var.region - containers = [{ - image = var.image - options = null - ports = null - resources = null - volume_mounts = null - }] + containers = { + default = { + image = var.image + } + } iam = { "roles/run.invoker" = ["allUsers"] } @@ -149,13 +158,11 @@ module "cloud_run_checkout" { project_id = module.project_svc1[0].project_id name = local.service_name_cr2 # "checkout" region = var.region - containers = [{ - image = var.image - options = null - ports = null - resources = null - volume_mounts = null - }] + containers = { + default = { + image = var.image + } + } iam = { "roles/run.invoker" = ["allUsers"] } diff --git a/blueprints/serverless/cloud-run-explore/main.tf b/blueprints/serverless/cloud-run-explore/main.tf index 04bb3cc8b8..3bc1fbc675 100644 --- a/blueprints/serverless/cloud-run-explore/main.tf +++ b/blueprints/serverless/cloud-run-explore/main.tf @@ -43,13 +43,11 @@ module "cloud_run" { project_id = module.project.project_id name = var.run_svc_name region = var.region - containers = [{ - image = var.image - options = null - ports = null - resources = null - volume_mounts = null - }] + containers = { + default = { + image = var.image + } + } iam = { "roles/run.invoker" = ["allUsers"] } diff --git a/modules/cloud-run/README.md b/modules/cloud-run/README.md index 5fde6ed47b..69318b9273 100644 --- a/modules/cloud-run/README.md +++ b/modules/cloud-run/README.md @@ -1,95 +1,124 @@ # Cloud Run Module -Cloud Run management, with support for IAM roles and optional Eventarc trigger creation. +Cloud Run management, with support for IAM roles, revision annotations and optional Eventarc trigger creation. ## Examples -### Environment variables +- [IAM and environment variables](#iam-and-environment-variables) +- [Mounting secrets as volumes](#mounting-secrets-as-volumes) +- [Revision annotations](#revision-annotations) +- [VPC Access Connector creation](#vpc-access-connector-creation) +- [Traffic split](#traffic-split) +- [Eventarc triggers](#eventarc-triggers) +- [Service account](#service-account) -This deploys a Cloud Run service and sets some environment variables. +### IAM and environment variables + +IAM bindings support the usual syntax. Container environment values can be declared as key-value strings or as references to Secret Manager secrets. Both can be combined as long as there's no duplication of keys: ```hcl module "cloud_run" { source = "./fabric/modules/cloud-run" project_id = "my-project" name = "hello" - containers = [{ - image = "us-docker.pkg.dev/cloudrun/container/hello" - options = { - command = null - args = null + containers = { + hello = { + image = "us-docker.pkg.dev/cloudrun/container/hello" env = { - "VAR1" : "VALUE1", - "VAR2" : "VALUE2", + VAR1 = "VALUE1" + VAR2 = "VALUE2" + } + env_from = { + SECRET1 = { + name = "credentials" + key = "1" + } } - env_from = null } - ports = null - resources = null - volume_mounts = null - }] + } + iam = { + "roles/run.invoker" = ["allUsers"] + } } -# tftest modules=1 resources=1 +# tftest modules=1 resources=2 inventory=simple.yaml ``` -### Environment variables (value read from secret) +### Mounting secrets as volumes ```hcl module "cloud_run" { source = "./fabric/modules/cloud-run" - project_id = "my-project" + project_id = var.project_id name = "hello" - containers = [{ - image = "us-docker.pkg.dev/cloudrun/container/hello" - options = { - command = null - args = null - env = null - env_from = { - "CREDENTIALS" : { - name = "credentials" - key = "1" - } + region = var.region + containers = { + hello = { + image = "us-docker.pkg.dev/cloudrun/container/hello" + volume_mounts = { + "credentials" = "/credentials" + } + } + } + volumes = { + credentials = { + name = "credentials" + secret_name = "credentials" + items = { + v1 = { path = "v1.txt" } } } - ports = null - resources = null - volume_mounts = null - }] + } } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 inventory=secrets.yaml ``` -### Secret mounted as volume +### Revision annotations + +Annotations can be specified via the `revision_annotations` variable: ```hcl module "cloud_run" { - source = "./fabric/modules/cloud-run" - project_id = var.project_id - name = "hello" - region = var.region - revision_name = "green" - containers = [{ - image = "us-docker.pkg.dev/cloudrun/container/hello" - options = null - ports = null - resources = null - volume_mounts = { - "credentials" : "/credentials" + source = "./fabric/modules/cloud-run" + project_id = var.project_id + name = "hello" + containers = { + hello = { + image = "us-docker.pkg.dev/cloudrun/container/hello" } - }] - volumes = [ - { - name = "credentials" - secret_name = "credentials" - items = [{ - key = "1" - path = "v1.txt" - }] + } + revision_annotations = { + autoscaling = { + max_scale = 10 + min_scale = 1 } - ] + cloudsql_unstances = ["sql-0", "sql-1"] + vpcaccess_connector = "foo" + vpcaccess_egress = "all-traffic" + } +} +# tftest modules=1 resources=1 inventory=revision-annotations.yaml +``` + +### VPC Access Connector creation + +If creation of a [VPC Access Connector](https://cloud.google.com/vpc/docs/serverless-vpc-access) is required, use the `vpc_connector_create` variable which also support optional attribtues for number of instances, machine type, and throughput (not shown here). The annotation to use the connector will be added automatically. + +```hcl +module "cloud_run" { + source = "./fabric/modules/cloud-run" + project_id = var.project_id + name = "hello" + containers = { + hello = { + image = "us-docker.pkg.dev/cloudrun/container/hello" + } + } + vpc_connector_create = { + ip_cidr_range = "10.10.10.0/24" + vpc_self_link = "projects/example/host/global/networks/host" + } } -# tftest modules=1 resources=1 +# tftest modules=1 resources=2 inventory=connector.yaml ``` ### Traffic split @@ -102,22 +131,22 @@ module "cloud_run" { project_id = "my-project" name = "hello" revision_name = "green" - containers = [{ - image = "us-docker.pkg.dev/cloudrun/container/hello" - options = null - ports = null - resources = null - volume_mounts = null - }] + containers = { + hello = { + image = "us-docker.pkg.dev/cloudrun/container/hello" + } + } traffic = { - "blue" = 25 - "green" = 75 + blue = { percent = 25 } + green = { percent = 75 } } } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 inventory=traffic.yaml ``` -### Eventarc trigger (Pub/Sub) +### Eventarc triggers + +#### PubSub This deploys a Cloud Run service that will be triggered when messages are published to Pub/Sub topics. @@ -126,22 +155,22 @@ module "cloud_run" { source = "./fabric/modules/cloud-run" project_id = "my-project" name = "hello" - containers = [{ - image = "us-docker.pkg.dev/cloudrun/container/hello" - options = null - ports = null - resources = null - volume_mounts = null - }] - pubsub_triggers = [ - "topic1", - "topic2" - ] + containers = { + hello = { + image = "us-docker.pkg.dev/cloudrun/container/hello" + } + } + eventarc_triggers = { + pubsub = { + topic-1 = "topic1" + topic-2 = "topic2" + } + } } -# tftest modules=1 resources=3 +# tftest modules=1 resources=3 inventory=eventarc.yaml ``` -### Eventarc trigger (Audit logs) +#### Audit logs This deploys a Cloud Run service that will be triggered when specific log events are written to Google Cloud audit logs. @@ -150,24 +179,24 @@ module "cloud_run" { source = "./fabric/modules/cloud-run" project_id = "my-project" name = "hello" - containers = [{ - image = "us-docker.pkg.dev/cloudrun/container/hello" - options = null - ports = null - resources = null - volume_mounts = null - }] - audit_log_triggers = [ - { - service_name = "cloudresourcemanager.googleapis.com" - method_name = "SetIamPolicy" + containers = { + hello = { + image = "us-docker.pkg.dev/cloudrun/container/hello" + } + } + eventarc_triggers = { + audit_log = { + setiampolicy = { + method = "SetIamPolicy" + service = "cloudresourcemanager.googleapis.com" + } } - ] + } } -# tftest modules=1 resources=2 +# tftest modules=1 resources=2 inventory=audit-logs.yaml ``` -### Service account management +### 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` value (default). @@ -176,16 +205,14 @@ module "cloud_run" { source = "./fabric/modules/cloud-run" project_id = "my-project" name = "hello" - containers = [{ - image = "us-docker.pkg.dev/cloudrun/container/hello" - options = null - ports = null - resources = null - volume_mounts = null - }] + containers = { + hello = { + image = "us-docker.pkg.dev/cloudrun/container/hello" + } + } service_account_create = true } -# tftest modules=1 resources=2 +# tftest modules=1 resources=2 inventory=service-account.yaml ``` To use an externally managed service account, pass its email in `service_account` and leave `service_account_create` to `false` (the default). @@ -195,16 +222,14 @@ module "cloud_run" { source = "./fabric/modules/cloud-run" project_id = "my-project" name = "hello" - containers = [{ - image = "us-docker.pkg.dev/cloudrun/container/hello" - options = null - ports = null - resources = null - volume_mounts = null - }] + containers = { + hello = { + image = "us-docker.pkg.dev/cloudrun/container/hello" + } + } service_account = "cloud-run@my-project.iam.gserviceaccount.com" } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 inventory=service-account-external.yaml ``` @@ -212,23 +237,24 @@ module "cloud_run" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [containers](variables.tf#L27) | Containers. | list(object({…})) | ✓ | | -| [name](variables.tf#L77) | Name used for cloud run service. | string | ✓ | | -| [project_id](variables.tf#L92) | Project id used for all resources. | string | ✓ | | -| [audit_log_triggers](variables.tf#L18) | Event arc triggers (Audit log). | list(object({…})) | | null | -| [iam](variables.tf#L59) | IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [ingress_settings](variables.tf#L65) | Ingress settings. | string | | null | -| [labels](variables.tf#L71) | Resource labels. | map(string) | | {} | -| [prefix](variables.tf#L82) | Optional prefix used for resource names. | string | | null | -| [pubsub_triggers](variables.tf#L97) | Eventarc triggers (Pub/Sub). | list(string) | | null | -| [region](variables.tf#L103) | Region used for all resources. | string | | "europe-west1" | -| [revision_annotations](variables.tf#L109) | Configure revision template annotations. | object({…}) | | null | -| [revision_name](variables.tf#L123) | Revision name. | string | | null | -| [service_account](variables.tf#L129) | Service account email. Unused if service account is auto-created. | string | | null | -| [service_account_create](variables.tf#L135) | Auto-create service account. | bool | | false | -| [traffic](variables.tf#L141) | Traffic. | map(number) | | null | -| [volumes](variables.tf#L147) | Volumes. | list(object({…})) | | null | -| [vpc_connector_create](variables.tf#L160) | Populate this to create a VPC connector. You can then refer to it in the template annotations. | object({…}) | | null | +| [name](variables.tf#L121) | Name used for cloud run service. | string | ✓ | | +| [project_id](variables.tf#L136) | Project id used for all resources. | string | ✓ | | +| [container_concurrency](variables.tf#L18) | Maximum allowed in-flight (concurrent) requests per container of the revision. | string | | null | +| [containers](variables.tf#L24) | Containers in arbitrary key => attributes format. | map(object({…})) | | {} | +| [eventarc_triggers](variables.tf#L91) | Event arc triggers for different sources. | object({…}) | | {} | +| [iam](variables.tf#L103) | IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [ingress_settings](variables.tf#L109) | Ingress settings. | string | | null | +| [labels](variables.tf#L115) | Resource labels. | map(string) | | {} | +| [prefix](variables.tf#L126) | Optional prefix used for resource names. | string | | null | +| [region](variables.tf#L141) | Region used for all resources. | string | | "europe-west1" | +| [revision_annotations](variables.tf#L147) | Configure revision template annotations. | object({…}) | | {} | +| [revision_name](variables.tf#L162) | Revision name. | string | | null | +| [service_account](variables.tf#L168) | Service account email. Unused if service account is auto-created. | string | | null | +| [service_account_create](variables.tf#L174) | Auto-create service account. | bool | | false | +| [timeout_seconds](variables.tf#L180) | Maximum duration the instance is allowed for responding to a request. | number | | null | +| [traffic](variables.tf#L186) | Traffic steering configuration. If revision name is null the latest revision will be used. | map(object({…})) | | {} | +| [volumes](variables.tf#L197) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | +| [vpc_connector_create](variables.tf#L211) | Populate this to create a VPC connector. You can then refer to it in the template annotations. | object({…}) | | null | ## Outputs diff --git a/modules/cloud-run/main.tf b/modules/cloud-run/main.tf index eab61d4a52..8ef52be5d5 100644 --- a/modules/cloud-run/main.tf +++ b/modules/cloud-run/main.tf @@ -21,10 +21,12 @@ locals { "run.googleapis.com/vpc-access-connector" = google_vpc_access_connector.connector.0.id } : ( - try(var.revision_annotations.vpcaccess_connector, null) == null + var.revision_annotations.vpcaccess_connector == null ? {} : { - "run.googleapis.com/vpc-access-connector" = var.revision_annotations.vpcaccess_connector + "run.googleapis.com/vpc-access-connector" = ( + var.revision_annotations.vpcaccess_connector + ) } ) ) @@ -35,20 +37,26 @@ locals { ) prefix = var.prefix == null ? "" : "${var.prefix}-" revision_annotations = merge( - try(var.revision_annotations.autoscaling.max_scale, null) == null ? {} : { - "autoscaling.knative.dev/maxScale" = var.revision_annotations.autoscaling.max_scale + try(var.revision_annotations.autoscaling, null) == null ? {} : { + "autoscaling.knative.dev/maxScale" = ( + var.revision_annotations.autoscaling.max_scale + ) }, try(var.revision_annotations.autoscaling.min_scale, null) == null ? {} : { - "autoscaling.knative.dev/minScale" = var.revision_annotations.autoscaling.min_scale + "autoscaling.knative.dev/minScale" = ( + var.revision_annotations.autoscaling.min_scale + ) }, - try(var.revision_annotations.cloudsql_instances, null) == null ? {} : { - "run.googleapis.com/cloudsql-instances" = join(",", coalesce( - var.revision_annotations.cloudsql_instances, [] - )) + length(var.revision_annotations.cloudsql_instances) == 0 ? {} : { + "run.googleapis.com/cloudsql-instances" = ( + join(",", var.revision_annotations.cloudsql_instances) + ) }, local._vpcaccess_annotation, - try(var.revision_annotations.vpcaccess_egress, null) == null ? {} : { - "run.googleapis.com/vpc-access-egress" = var.revision_annotations.vpcaccess_egress + var.revision_annotations.vpcaccess_egress == null ? {} : { + "run.googleapis.com/vpc-access-egress" = ( + var.revision_annotations.vpcaccess_egress + ) }, ) revision_name = ( @@ -69,12 +77,21 @@ locals { } resource "google_vpc_access_connector" "connector" { - count = local.vpc_connector_create ? 1 : 0 - project = var.project_id - name = var.vpc_connector_create.name - region = var.region - ip_cidr_range = var.vpc_connector_create.ip_cidr_range - network = var.vpc_connector_create.vpc_self_link + count = local.vpc_connector_create ? 1 : 0 + project = var.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.vpc_self_link + machine_type = var.vpc_connector_create.machine_type + max_instances = var.vpc_connector_create.instances.max + max_throughput = var.vpc_connector_create.throughput.max + min_instances = var.vpc_connector_create.instances.min + min_throughput = var.vpc_connector_create.throughput.min } resource "google_cloud_run_service" "service" { @@ -85,54 +102,75 @@ resource "google_cloud_run_service" "service" { template { spec { + container_concurrency = var.container_concurrency + service_account_name = local.service_account_email + timeout_seconds = var.timeout_seconds dynamic "containers" { - for_each = var.containers == null ? {} : { - for i, container in var.containers : i => container - } + for_each = var.containers content { image = containers.value.image - command = try(containers.value.options.command, null) - args = try(containers.value.options.args, null) + args = containers.value.args + command = containers.value.command dynamic "env" { - for_each = ( - try(containers.value.options.env, null) == null - ? {} - : containers.value.options.env - ) + for_each = containers.value.env content { name = env.key value = env.value } } dynamic "env" { - for_each = ( - try(containers.value.options.env_from, null) == null - ? {} - : containers.value.options.env_from - ) + for_each = containers.value.env_from_key content { name = env.key value_from { secret_key_ref { - name = env.value.name key = env.value.key + name = env.value.name } } } } - dynamic "ports" { - for_each = ( - containers.value.ports == null - ? {} - : { - for port in containers.value.ports : - "${port.name}-${port.container_port}" => port + dynamic "liveness_probe" { + for_each = containers.value.liveness_probe == null ? [] : [""] + content { + failure_threshold = containers.value.liveness_probe.failure_threshold + initial_delay_seconds = containers.value.liveness_probe.initial_delay_seconds + period_seconds = containers.value.liveness_probe.period_seconds + timeout_seconds = containers.value.liveness_probe.timeout_seconds + dynamic "grpc" { + for_each = ( + containers.value.liveness_probe.action.grpc == null ? [] : [""] + ) + content { + port = containers.value.liveness_probe.action.grpc.port + service = containers.value.liveness_probe.action.grpc.service + } } - ) + dynamic "http_get" { + for_each = ( + containers.value.liveness_probe.action.http_get == null ? [] : [""] + ) + content { + path = containers.value.liveness_probe.action.http_get.path + dynamic "http_headers" { + for_each = ( + containers.value.liveness_probe.action.http_get.http_headers + ) + content { + name = http_headers.key + value = http_headers.value + } + } + } + } + } + } + dynamic "ports" { + for_each = containers.value.ports content { + container_port = ports.value.container_port name = ports.value.name protocol = ports.value.protocol - container_port = ports.value.container_port } } dynamic "resources" { @@ -142,12 +180,51 @@ resource "google_cloud_run_service" "service" { requests = containers.value.resources.requests } } + dynamic "startup_probe" { + for_each = containers.value.startup_probe == null ? [] : [""] + content { + failure_threshold = containers.value.startup_probe.failure_threshold + initial_delay_seconds = containers.value.startup_probe.initial_delay_seconds + period_seconds = containers.value.startup_probe.period_seconds + timeout_seconds = containers.value.startup_probe.timeout_seconds + dynamic "grpc" { + for_each = ( + containers.value.startup_probe.action.grpc == null ? [] : [""] + ) + content { + port = containers.value.startup_probe.action.grpc.port + service = containers.value.startup_probe.action.grpc.service + } + } + dynamic "http_get" { + for_each = ( + containers.value.startup_probe.action.http_get == null ? [] : [""] + ) + content { + path = containers.value.startup_probe.action.http_get.path + dynamic "http_headers" { + for_each = ( + containers.value.startup_probe.action.http_get.http_headers + ) + content { + name = http_headers.key + value = http_headers.value + } + } + } + } + dynamic "tcp_socket" { + for_each = ( + containers.value.startup_probe.action.tcp_socket == null ? [] : [""] + ) + content { + port = containers.value.startup_probe.action.tcp_socket.port + } + } + } + } dynamic "volume_mounts" { - for_each = ( - containers.value.volume_mounts == null - ? {} - : containers.value.volume_mounts - ) + for_each = containers.value.volume_mounts content { name = volume_mounts.key mount_path = volume_mounts.value @@ -155,20 +232,19 @@ resource "google_cloud_run_service" "service" { } } } - service_account_name = local.service_account_email dynamic "volumes" { - for_each = var.volumes == null ? [] : var.volumes + for_each = var.volumes content { - name = volumes.value.name + name = volumes.key secret { - secret_name = volumes.value.secret_name + secret_name = volumes.value.secret_name + default_mode = volumes.value.default_mode dynamic "items" { - for_each = ( - volumes.value.items == null ? [] : volumes.value.items - ) + for_each = volumes.value.items content { - key = items.value.key + key = items.key path = items.value.path + mode = items.value.mode } } } @@ -186,10 +262,16 @@ resource "google_cloud_run_service" "service" { } dynamic "traffic" { - for_each = var.traffic == null ? {} : var.traffic + for_each = var.traffic content { - percent = traffic.value - revision_name = "${var.name}-${traffic.key}" + percent = traffic.value.percent + latest_revision = traffic.value.latest == true + revision_name = ( + traffic.value.latest == true + ? null + : "${var.name}-${traffic.key}" + ) + tag = traffic.value.tag } } @@ -217,11 +299,8 @@ resource "google_service_account" "service_account" { } resource "google_eventarc_trigger" "audit_log_triggers" { - for_each = var.audit_log_triggers == null ? {} : { - for trigger in var.audit_log_triggers : - "${trigger.service_name}-${trigger.method_name}" => trigger - } - name = "${local.prefix}${each.key}-audit-log-trigger" + for_each = var.eventarc_triggers.audit_log + name = "${local.prefix}audit-log-${each.key}" location = google_cloud_run_service.service.location project = google_cloud_run_service.service.project matching_criteria { @@ -230,11 +309,11 @@ resource "google_eventarc_trigger" "audit_log_triggers" { } matching_criteria { attribute = "serviceName" - value = each.value.service_name + value = each.value.service } matching_criteria { attribute = "methodName" - value = each.value.method_name + value = each.value.method } destination { cloud_run_service { @@ -245,24 +324,17 @@ resource "google_eventarc_trigger" "audit_log_triggers" { } resource "google_eventarc_trigger" "pubsub_triggers" { - for_each = var.pubsub_triggers == null ? [] : toset(var.pubsub_triggers) - name = ( - each.value == "" - ? "${local.prefix}default-pubsub-trigger" - : "${local.prefix}${each.value}-pubsub-trigger" - ) + for_each = var.eventarc_triggers.pubsub + name = "${local.prefix}pubsub-${each.key}" location = google_cloud_run_service.service.location project = google_cloud_run_service.service.project matching_criteria { attribute = "type" value = "google.cloud.pubsub.topic.v1.messagePublished" } - dynamic "transport" { - for_each = each.value == null ? [] : [""] - content { - pubsub { - topic = each.value - } + transport { + pubsub { + topic = each.value } } destination { diff --git a/modules/cloud-run/variables.tf b/modules/cloud-run/variables.tf index 8029f1c411..e82576fe98 100644 --- a/modules/cloud-run/variables.tf +++ b/modules/cloud-run/variables.tf @@ -15,45 +15,89 @@ * limitations under the License. */ -variable "audit_log_triggers" { - description = "Event arc triggers (Audit log)." - type = list(object({ - service_name = string - method_name = string - })) - default = null +variable "container_concurrency" { + description = "Maximum allowed in-flight (concurrent) requests per container of the revision." + type = string + default = null } variable "containers" { - description = "Containers." - type = list(object({ - image = string - options = object({ - command = list(string) - args = list(string) - env = map(string) - env_from = map(object({ - key = string - name = string - })) - }) - resources = object({ - limits = object({ + description = "Containers in arbitrary key => attributes format." + type = map(object({ + image = string + args = optional(list(string)) + command = optional(list(string)) + env = optional(map(string), {}) + env_from_key = optional(map(object({ + key = string + name = string + })), {}) + liveness_probe = optional(object({ + action = object({ + grcp = optional(object({ + port = optional(number) + service = optional(string) + })) + http_get = optional(object({ + http_headers = optional(map(string), {}) + path = optional(string) + })) + }) + failure_threshold = optional(number) + initial_delay_seconds = optional(number) + period_seconds = optional(number) + timeout_seconds = optional(number) + })) + ports = optional(map(object({ + container_port = optional(number) + name = optional(string) + protocol = optional(string) + })), {}) + resources = optional(object({ + limits = optional(object({ cpu = string memory = string - }) - requests = object({ + })) + requests = optional(object({ cpu = string memory = string + })) + })) + startup_probe = optional(object({ + action = object({ + grcp = optional(object({ + port = optional(number) + service = optional(string) + })) + http_get = optional(object({ + http_headers = optional(map(string), {}) + path = optional(string) + })) + tcp_socket = optional(object({ + port = optional(number) + })) }) - }) - ports = list(object({ - name = string - protocol = string - container_port = string + failure_threshold = optional(number) + initial_delay_seconds = optional(number) + period_seconds = optional(number) + timeout_seconds = optional(number) })) - volume_mounts = map(string) + volume_mounts = optional(map(string), {}) })) + default = {} + nullable = false +} + +variable "eventarc_triggers" { + description = "Event arc triggers for different sources." + type = object({ + audit_log = optional(map(object({ + method = string + service = string + })), {}) + pubsub = optional(map(string), {}) + }) + default = {} } variable "iam" { @@ -94,12 +138,6 @@ variable "project_id" { type = string } -variable "pubsub_triggers" { - description = "Eventarc triggers (Pub/Sub)." - type = list(string) - default = null -} - variable "region" { description = "Region used for all resources." type = string @@ -109,15 +147,16 @@ variable "region" { variable "revision_annotations" { description = "Configure revision template annotations." type = object({ - autoscaling = object({ + autoscaling = optional(object({ max_scale = number min_scale = number - }) - cloudsql_instances = list(string) - vpcaccess_connector = string - vpcaccess_egress = string + })) + cloudsql_instances = optional(list(string), []) + vpcaccess_connector = optional(string) + vpcaccess_egress = optional(string) }) - default = null + default = {} + nullable = false } variable "revision_name" { @@ -138,31 +177,52 @@ variable "service_account_create" { default = false } -variable "traffic" { - description = "Traffic." - type = map(number) +variable "timeout_seconds" { + description = "Maximum duration the instance is allowed for responding to a request." + type = number default = null } +variable "traffic" { + description = "Traffic steering configuration. If revision name is null the latest revision will be used." + type = map(object({ + percent = number + latest = optional(bool) + tag = optional(string) + })) + default = {} + nullable = false +} + variable "volumes" { - description = "Volumes." - type = list(object({ - name = string - secret_name = string - items = list(object({ - key = string + description = "Named volumes in containers in name => attributes format." + type = map(object({ + secret_name = string + default_mode = optional(string) + items = optional(map(object({ path = string - })) + mode = optional(string) + }))) })) - default = null + default = {} + nullable = false } variable "vpc_connector_create" { description = "Populate this to create a VPC connector. You can then refer to it in the template annotations." type = object({ ip_cidr_range = string - name = string vpc_self_link = string + machine_type = optional(string) + name = optional(string) + instances = optional(object({ + max = optional(number) + min = optional(number) + }), {}) + throughput = optional(object({ + max = optional(number) + min = optional(number) + }), {}) }) default = null } diff --git a/tests/modules/cloud_run/examples/audit-logs.yaml b/tests/modules/cloud_run/examples/audit-logs.yaml new file mode 100644 index 0000000000..efdce1a76a --- /dev/null +++ b/tests/modules/cloud_run/examples/audit-logs.yaml @@ -0,0 +1,42 @@ +# Copyright 2023 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_service.service: {} + module.cloud_run.google_eventarc_trigger.audit_log_triggers["setiampolicy"]: + destination: + - cloud_function: null + cloud_run_service: + - path: null + region: europe-west1 + service: hello + gke: [] + workflow: null + location: europe-west1 + matching_criteria: + - attribute: methodName + operator: '' + value: SetIamPolicy + - attribute: serviceName + operator: '' + value: cloudresourcemanager.googleapis.com + - attribute: type + operator: '' + value: google.cloud.audit.log.v1.written + name: audit-log-setiampolicy + project: my-project + +counts: + google_cloud_run_service: 1 + google_eventarc_trigger: 1 diff --git a/tests/modules/cloud_run/examples/connector.yaml b/tests/modules/cloud_run/examples/connector.yaml new file mode 100644 index 0000000000..ce2ec6fc32 --- /dev/null +++ b/tests/modules/cloud_run/examples/connector.yaml @@ -0,0 +1,48 @@ +# Copyright 2023 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_service.service: + autogenerate_revision_name: false + location: europe-west1 + name: hello + project: project-id + template: + - metadata: + - labels: null + spec: + - containers: + - args: null + command: null + env: [] + env_from: [] + image: us-docker.pkg.dev/cloudrun/container/hello + liveness_probe: [] + volume_mounts: [] + working_dir: null + volumes: [] + module.cloud_run.google_vpc_access_connector.connector[0]: + ip_cidr_range: 10.10.10.0/24 + machine_type: e2-micro + max_throughput: 300 + min_throughput: 200 + name: hello + network: projects/example/host/global/networks/host + project: project-id + region: europe-west1 + subnet: [] + +counts: + google_cloud_run_service: 1 + google_vpc_access_connector: 1 diff --git a/tests/modules/cloud_run/examples/eventarc.yaml b/tests/modules/cloud_run/examples/eventarc.yaml new file mode 100644 index 0000000000..3f4eae9a8d --- /dev/null +++ b/tests/modules/cloud_run/examples/eventarc.yaml @@ -0,0 +1,58 @@ +# Copyright 2023 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_service.service: {} + module.cloud_run.google_eventarc_trigger.pubsub_triggers["topic-1"]: + destination: + - cloud_function: null + cloud_run_service: + - path: null + region: europe-west1 + service: hello + gke: [] + workflow: null + location: europe-west1 + matching_criteria: + - attribute: type + operator: '' + value: google.cloud.pubsub.topic.v1.messagePublished + name: pubsub-topic-1 + project: my-project + transport: + - pubsub: + - topic: topic1 + module.cloud_run.google_eventarc_trigger.pubsub_triggers["topic-2"]: + destination: + - cloud_function: null + cloud_run_service: + - path: null + region: europe-west1 + service: hello + gke: [] + workflow: null + location: europe-west1 + matching_criteria: + - attribute: type + operator: '' + value: google.cloud.pubsub.topic.v1.messagePublished + name: pubsub-topic-2 + project: my-project + transport: + - pubsub: + - topic: topic2 + +counts: + google_cloud_run_service: 1 + google_eventarc_trigger: 2 diff --git a/tests/modules/cloud_run/examples/revision-annotations.yaml b/tests/modules/cloud_run/examples/revision-annotations.yaml new file mode 100644 index 0000000000..21062d3e84 --- /dev/null +++ b/tests/modules/cloud_run/examples/revision-annotations.yaml @@ -0,0 +1,42 @@ +# Copyright 2023 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_service.service: + template: + - metadata: + - annotations: + autoscaling.knative.dev/maxScale: '10' + autoscaling.knative.dev/minScale: '1' + run.googleapis.com/vpc-access-connector: foo + run.googleapis.com/vpc-access-egress: all-traffic + labels: null + spec: + - containers: + - args: null + command: null + env: [] + env_from: [] + image: us-docker.pkg.dev/cloudrun/container/hello + liveness_probe: [] + volume_mounts: [] + working_dir: null + volumes: [] + +counts: + google_cloud_run_service: 1 + modules: 1 + resources: 1 + +outputs: {} diff --git a/tests/modules/cloud_run/examples/secrets.yaml b/tests/modules/cloud_run/examples/secrets.yaml new file mode 100644 index 0000000000..f1209f724c --- /dev/null +++ b/tests/modules/cloud_run/examples/secrets.yaml @@ -0,0 +1,43 @@ +# Copyright 2023 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_service.service: + template: + - metadata: + - labels: null + spec: + - containers: + - args: null + command: null + env: [] + env_from: [] + image: us-docker.pkg.dev/cloudrun/container/hello + liveness_probe: [] + volume_mounts: + - mount_path: /credentials + name: credentials + working_dir: null + volumes: + - name: credentials + secret: + - default_mode: null + items: + - key: v1 + mode: null + path: v1.txt + secret_name: credentials + +counts: + google_cloud_run_service: 1 diff --git a/tests/modules/cloud_run/fixture/bundle/main.py b/tests/modules/cloud_run/examples/service-account-external.yaml similarity index 50% rename from tests/modules/cloud_run/fixture/bundle/main.py rename to tests/modules/cloud_run/examples/service-account-external.yaml index 6d6d1266c3..808bf00c72 100644 --- a/tests/modules/cloud_run/fixture/bundle/main.py +++ b/tests/modules/cloud_run/examples/service-account-external.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,3 +11,24 @@ # 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_service.service: + template: + - metadata: + - labels: null + spec: + - containers: + - args: null + command: null + env: [] + env_from: [] + image: us-docker.pkg.dev/cloudrun/container/hello + liveness_probe: [] + volume_mounts: [] + working_dir: null + service_account_name: cloud-run@my-project.iam.gserviceaccount.com + volumes: [] + +counts: + google_cloud_run_service: 1 diff --git a/tests/modules/cloud_run/examples/service-account.yaml b/tests/modules/cloud_run/examples/service-account.yaml new file mode 100644 index 0000000000..1e2ed48ced --- /dev/null +++ b/tests/modules/cloud_run/examples/service-account.yaml @@ -0,0 +1,43 @@ +# Copyright 2023 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_service.service: + autogenerate_revision_name: false + location: europe-west1 + metadata: + - {} + name: hello + project: my-project + template: + - metadata: + - labels: null + spec: + - containers: + - args: null + command: null + env: [] + env_from: [] + image: us-docker.pkg.dev/cloudrun/container/hello + liveness_probe: [] + volume_mounts: [] + working_dir: null + volumes: [] + module.cloud_run.google_service_account.service_account[0]: + account_id: tf-cr-hello + project: my-project + +counts: + google_cloud_run_service: 1 + google_service_account: 1 diff --git a/tests/modules/cloud_run/examples/simple.yaml b/tests/modules/cloud_run/examples/simple.yaml new file mode 100644 index 0000000000..32c38ba402 --- /dev/null +++ b/tests/modules/cloud_run/examples/simple.yaml @@ -0,0 +1,52 @@ +# Copyright 2023 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_service.service: + autogenerate_revision_name: false + location: europe-west1 + name: hello + project: my-project + template: + - metadata: + - labels: null + spec: + - containers: + - args: null + command: null + env: + - name: VAR1 + value: VALUE1 + value_from: [] + - name: VAR2 + value: VALUE2 + value_from: [] + env_from: [] + image: us-docker.pkg.dev/cloudrun/container/hello + liveness_probe: [] + volume_mounts: [] + working_dir: null + volumes: [] + module.cloud_run.google_cloud_run_service_iam_binding.binding["roles/run.invoker"]: + condition: [] + location: europe-west1 + members: + - allUsers + project: my-project + role: roles/run.invoker + service: hello + +counts: + google_cloud_run_service: 1 + google_cloud_run_service_iam_binding: 1 diff --git a/tests/modules/cloud_run/__init__.py b/tests/modules/cloud_run/examples/traffic.yaml similarity index 62% rename from tests/modules/cloud_run/__init__.py rename to tests/modules/cloud_run/examples/traffic.yaml index 6d6d1266c3..df20f5eeac 100644 --- a/tests/modules/cloud_run/__init__.py +++ b/tests/modules/cloud_run/examples/traffic.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,3 +11,18 @@ # 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_service.service: + traffic: + - latest_revision: false + percent: 25 + revision_name: hello-blue + tag: null + - latest_revision: false + percent: 75 + revision_name: hello-green + tag: null + +counts: + google_cloud_run_service: 1 diff --git a/tests/modules/cloud_run/fixture/main.tf b/tests/modules/cloud_run/fixture/main.tf deleted file mode 100644 index 4692c22c7f..0000000000 --- a/tests/modules/cloud_run/fixture/main.tf +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2022 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 "revision_annotations" { - description = "Configure revision template annotations." - type = any - default = null -} - -variable "vpc_connector_create" { - description = "Populate this to create a VPC connector. You can then refer to it in the template annotations." - type = any - default = null -} - -module "cloud_run" { - source = "../../../../modules/cloud-run" - project_id = "my-project" - name = "hello" - audit_log_triggers = [ - { - "service_name" : "cloudresourcemanager.googleapis.com", - "method_name" : "SetIamPolicy" - } - ] - containers = [{ - image = "us-docker.pkg.dev/cloudrun/container/hello" - options = null - ports = null - resources = null - volume_mounts = null - }] - iam = { - "roles/run.invoker" = ["allUsers"] - } - pubsub_triggers = [ - "topic1", - "topic2" - ] - revision_name = "blue" - revision_annotations = var.revision_annotations - vpc_connector_create = var.vpc_connector_create -} diff --git a/tests/modules/cloud_run/test_plan.py b/tests/modules/cloud_run/test_plan.py deleted file mode 100644 index 0671097ae6..0000000000 --- a/tests/modules/cloud_run/test_plan.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright 2022 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. - -import pytest - - -@pytest.fixture -def resources(plan_runner): - _, resources = plan_runner() - return resources - - -def test_resource_count(resources): - "Test number of resources created." - assert len(resources) == 5 - - -def test_iam(resources): - "Test IAM binding resources." - bindings = [ - r['values'] - for r in resources - if r['type'] == 'google_cloud_run_service_iam_binding' - ] - assert len(bindings) == 1 - assert bindings[0]['role'] == 'roles/run.invoker' - - -def test_audit_log_triggers(resources): - "Test audit logs Eventarc trigger resources." - audit_log_triggers = [ - r['values'] - for r in resources - if r['type'] == 'google_eventarc_trigger' and - r['name'] == 'audit_log_triggers' - ] - assert len(audit_log_triggers) == 1 - - -def test_pubsub_triggers(resources): - "Test Pub/Sub Eventarc trigger resources." - pubsub_triggers = [ - r['values'] for r in resources if - r['type'] == 'google_eventarc_trigger' and r['name'] == 'pubsub_triggers' - ] - assert len(pubsub_triggers) == 2 - - -def test_revision_annotations(plan_runner): - revision_annotations = '''{ - autoscaling = null - cloudsql_instances = ["a", "b"] - vpcaccess_connector = "foo" - vpcaccess_egress = "all-traffic" - }''' - _, resources = plan_runner(revision_annotations=revision_annotations) - r = [ - r['values'] for r in resources if r['type'] == 'google_cloud_run_service' - ][0] - assert r['template'][0]['metadata'][0]['annotations'] == { - 'run.googleapis.com/cloudsql-instances': 'a,b', - 'run.googleapis.com/vpc-access-connector': 'foo', - 'run.googleapis.com/vpc-access-egress': 'all-traffic' - } - - -def test_revision_annotations_autoscaling(plan_runner): - revision_annotations = '''{ - autoscaling = {max_scale = 5, min_scale = 1} - cloudsql_instances = null - vpcaccess_connector = null - vpcaccess_egress = null - }''' - _, resources = plan_runner(revision_annotations=revision_annotations) - r = [ - r['values'] for r in resources if r['type'] == 'google_cloud_run_service' - ][0] - assert r['template'][0]['metadata'][0]['annotations'] == { - 'autoscaling.knative.dev/maxScale': '5', - 'autoscaling.knative.dev/minScale': '1' - } - - -def test_revision_annotations_none(resources): - r = [ - r['values'] for r in resources if r['type'] == 'google_cloud_run_service' - ][0] - assert r['template'][0]['metadata'][0].get('annotations') is None - - -def test_vpc_connector_create(plan_runner): - vpc_connector_create = '''{ - ip_cidr_range = "10.10.10.0/24", name = "foo", vpc_self_link = "foo-vpc" - }''' - _, resources = plan_runner(vpc_connector_create=vpc_connector_create) - assert any(r['type'] == 'google_vpc_access_connector' for r in resources)