diff --git a/Makefile b/Makefile index 0ca271d2..044ac03d 100644 --- a/Makefile +++ b/Makefile @@ -36,6 +36,7 @@ docker_test_lint: .PHONY: docker_generate_docs docker_generate_docs: docker run --rm -it \ + -e ENABLE_BPMETADATA \ -v $(CURDIR):/workspace \ $(REGISTRY_URL)/${DOCKER_IMAGE_DEVELOPER_TOOLS}:${DOCKER_TAG_VERSION_DEVELOPER_TOOLS} \ /bin/bash -c 'source /usr/local/bin/task_helper_functions.sh && generate_docs' diff --git a/examples/v2/README.md b/examples/v2/README.md new file mode 100644 index 00000000..5298a056 --- /dev/null +++ b/examples/v2/README.md @@ -0,0 +1,63 @@ +# Cloud Run Service using v2 API Example + +This example showcases the basic deployment of containerized applications on Cloud Run and IAM policy for the service. + +The resources/services/activations/deletions that this example will create/trigger are: + +* Creates a Cloud Run service with provided name and container +* Creates a Service Account to be used by Cloud Run Service. + +## Assumptions and Prerequisites + +This example assumes that below mentioned prerequisites are in place before consuming the example. + +* All required APIs are enabled in the GCP Project + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| project\_id | The project ID to deploy to | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| observed\_generation | The generation of this Service currently serving traffic. | +| project\_id | Project ID of the service | +| service\_id | Unique Identifier for the created service with format projects/{{project}}/locations/{{location}}/services/{{name}} | +| service\_location | Location in which the Cloud Run service was created | +| service\_name | Name of the created service | +| service\_uri | The URL on which the deployed service is available | +| traffic\_statuses | Detailed status information for corresponding traffic targets. | + + + +## Requirements + +These sections describe requirements for using this example. + +### Software + +* [Terraform](https://www.terraform.io/downloads.html) ~> v0.13+ +* [Terraform Provider for GCP](https://github.com/terraform-providers/terraform-provider-google) ~> v5.0+ +* [Terraform Provider for GCP Beta](https://github.com/terraform-providers/terraform-provider-google-beta) ~> + v5.0+ + +### Service Account + +A service account can be used with required roles to execute this example: + +* Cloud Run Admin: `roles/run.admin` + +Know more about [Cloud Run Deployment Permissions](https://cloud.google.com/run/docs/reference/iam/roles#additional-configuration). + +The [Project Factory module](https://registry.terraform.io/modules/terraform-google-modules/project-factory/google/latest) and the +[IAM module](https://registry.terraform.io/modules/terraform-google-modules/iam/google/latest) may be used in combination to provision a service account with the necessary roles applied. + +### APIs + +A project with the following APIs enabled must be used to host the main resource of this example: + +* Google Cloud Run: `run.googleapis.com` diff --git a/examples/v2/main.tf b/examples/v2/main.tf new file mode 100644 index 00000000..867ea211 --- /dev/null +++ b/examples/v2/main.tf @@ -0,0 +1,39 @@ +/** + * 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. + */ + +module "service_account" { + source = "terraform-google-modules/service-accounts/google" + version = "~> 4.2" + project_id = var.project_id + prefix = "sa-cloud-run" + names = ["simple"] +} + +module "cloud_run_v2" { + source = "GoogleCloudPlatform/cloud-run/google//modules/v2" + version = "~> 0.10" + + service_name = "ci-cloud-run-v2" + project_id = var.project_id + location = "us-central1" + containers = [ + { + container_image = "us-docker.pkg.dev/cloudrun/container/hello" + container_name = "hello-world" + } + ] + service_account = module.service_account.email +} diff --git a/examples/v2/outputs.tf b/examples/v2/outputs.tf new file mode 100644 index 00000000..73701062 --- /dev/null +++ b/examples/v2/outputs.tf @@ -0,0 +1,50 @@ +/** + * 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. + */ + +output "project_id" { + value = module.cloud_run_v2.project_id + description = "Project ID of the service" +} + +output "service_name" { + value = module.cloud_run_v2.service_name + description = "Name of the created service" +} + +output "service_uri" { + value = module.cloud_run_v2.service_uri + description = "The URL on which the deployed service is available" +} + +output "service_id" { + value = module.cloud_run_v2.service_id + description = "Unique Identifier for the created service with format projects/{{project}}/locations/{{location}}/services/{{name}}" +} + +output "service_location" { + value = module.cloud_run_v2.location + description = "Location in which the Cloud Run service was created" +} + +output "traffic_statuses" { + value = module.cloud_run_v2.traffic_statuses + description = "Detailed status information for corresponding traffic targets." +} + +output "observed_generation" { + value = module.cloud_run_v2.observed_generation + description = "The generation of this Service currently serving traffic." +} diff --git a/examples/v2/variables.tf b/examples/v2/variables.tf new file mode 100644 index 00000000..f284ef4d --- /dev/null +++ b/examples/v2/variables.tf @@ -0,0 +1,20 @@ +/** + * 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 "project_id" { + description = "The project ID to deploy to" + type = string +} diff --git a/metadata.yaml b/metadata.yaml new file mode 100644 index 00000000..7c7e2281 --- /dev/null +++ b/metadata.yaml @@ -0,0 +1,314 @@ +# 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. + +apiVersion: blueprints.cloud.google.com/v1alpha1 +kind: BlueprintMetadata +metadata: + name: terraform-google-cloud-run + annotations: + config.kubernetes.io/local-config: "true" +spec: + info: + title: Terraform Cloud Run Module + source: + repo: https://github.com/GoogleCloudPlatform/terraform-google-cloud-run.git + sourceType: git + version: 0.11.0 + actuationTool: + flavor: Terraform + version: ">= 1.3" + description: {} + content: + subBlueprints: + - name: job-exec + location: modules/job-exec + - name: secure-cloud-run + location: modules/secure-cloud-run + - name: secure-cloud-run-core + location: modules/secure-cloud-run-core + - name: secure-cloud-run-security + location: modules/secure-cloud-run-security + - name: secure-serverless-harness + location: modules/secure-serverless-harness + - name: secure-serverless-net + location: modules/secure-serverless-net + - name: service-project-factory + location: modules/service-project-factory + - name: v2 + location: modules/v2 + examples: + - name: cloud_run_vpc_connector + location: examples/cloud_run_vpc_connector + - name: secure_cloud_run + location: examples/secure_cloud_run + - name: secure_cloud_run_standalone + location: examples/secure_cloud_run_standalone + - name: simple_cloud_run + location: examples/simple_cloud_run + - name: simple_cloud_run_with_cmek + location: examples/simple_cloud_run_with_cmek + - name: simple_job_exec + location: examples/simple_job_exec + - name: v2 + location: examples/v2 + interfaces: + variables: + - name: argument + description: Arguments passed to the ENTRYPOINT command, include these only if image entrypoint needs arguments + varType: list(string) + defaultValue: [] + - name: certificate_mode + description: The mode of the certificate (NONE or AUTOMATIC) + varType: string + defaultValue: NONE + - name: container_command + description: Leave blank to use the ENTRYPOINT command defined in the container image, include these only if image entrypoint should be overwritten + varType: list(string) + defaultValue: [] + - name: container_concurrency + description: Concurrent request limits to the service + varType: number + - name: domain_map_annotations + description: Annotations to the domain map + varType: map(string) + defaultValue: {} + - name: domain_map_labels + description: A set of key/value label pairs to assign to the Domain mapping + varType: map(string) + defaultValue: {} + - name: encryption_key + description: CMEK encryption key self-link expected in the format projects/PROJECT/locations/LOCATION/keyRings/KEY-RING/cryptoKeys/CRYPTO-KEY. + varType: string + - name: env_secret_vars + description: "[Beta] Environment variables (Secret Manager)" + varType: |- + list(object({ + name = string + value_from = set(object({ + secret_key_ref = map(string) + })) + })) + defaultValue: [] + - name: env_vars + description: Environment variables (cleartext) + varType: |- + list(object({ + value = string + name = string + })) + defaultValue: [] + - name: force_override + description: Option to force override existing mapping + varType: bool + defaultValue: false + - name: generate_revision_name + description: Option to enable revision name generation + varType: bool + defaultValue: true + - name: image + description: GCR hosted image URL to deploy + varType: string + required: true + - name: limits + description: Resource limits to the container + varType: map(string) + - name: liveness_probe + description: | + Periodic probe of container liveness. Container will be restarted if the probe fails. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + varType: |- + object({ + failure_threshold = optional(number, null) + initial_delay_seconds = optional(number, null) + timeout_seconds = optional(number, null) + period_seconds = optional(number, null) + http_get = optional(object({ + path = optional(string) + http_headers = optional(list(object({ + name = string + value = string + })), null) + }), null) + grpc = optional(object({ + port = optional(number) + service = optional(string) + }), null) + }) + - name: location + description: Cloud Run service deployment location + varType: string + required: true + - name: members + description: Users/SAs to be given invoker access to the service + varType: list(string) + defaultValue: [] + - name: ports + description: Port which the container listens to (http1 or h2c) + varType: |- + object({ + name = string + port = number + }) + defaultValue: + name: http1 + port: 8080 + - name: project_id + description: The project ID to deploy to + varType: string + required: true + - name: requests + description: Resource requests to the container + varType: map(string) + defaultValue: {} + - name: service_account_email + description: Service Account email needed for the service + varType: string + defaultValue: "" + - name: service_annotations + description: Annotations to the service. Acceptable values all, internal, internal-and-cloud-load-balancing + varType: map(string) + defaultValue: + run.googleapis.com/ingress: all + - name: service_labels + description: A set of key/value label pairs to assign to the service + varType: map(string) + defaultValue: {} + - name: service_name + description: The name of the Cloud Run service to create + varType: string + required: true + - name: startup_probe + description: | + Startup probe of application within the container. + All other probes are disabled if a startup probe is provided, until it succeeds. + Container will not be added to service endpoints if the probe fails. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + varType: |- + object({ + failure_threshold = optional(number, null) + initial_delay_seconds = optional(number, null) + timeout_seconds = optional(number, null) + period_seconds = optional(number, null) + http_get = optional(object({ + path = optional(string) + http_headers = optional(list(object({ + name = string + value = string + })), null) + }), null) + tcp_socket = optional(object({ + port = optional(number) + }), null) + grpc = optional(object({ + port = optional(number) + service = optional(string) + }), null) + }) + - name: template_annotations + description: Annotations to the container metadata including VPC Connector and SQL. See [more details](https://cloud.google.com/run/docs/reference/rpc/google.cloud.run.v1#revisiontemplate) + varType: map(string) + defaultValue: + autoscaling.knative.dev/maxScale: 2 + autoscaling.knative.dev/minScale: 1 + generated-by: terraform + run.googleapis.com/client-name: terraform + - name: template_labels + description: A set of key/value label pairs to assign to the container metadata + varType: map(string) + defaultValue: {} + - name: timeout_seconds + description: Timeout for each request + varType: number + defaultValue: 120 + - name: traffic_split + description: Managing traffic routing to the service + varType: |- + list(object({ + latest_revision = bool + percent = number + revision_name = string + tag = string + })) + defaultValue: + - latest_revision: true + percent: 100 + revision_name: v1-0-0 + tag: null + - name: verified_domain_name + description: List of Custom Domain Name + varType: list(string) + defaultValue: [] + - name: volume_mounts + description: "[Beta] Volume Mounts to be attached to the container (when using secret)" + varType: |- + list(object({ + mount_path = string + name = string + })) + defaultValue: [] + - name: volumes + description: "[Beta] Volumes needed for environment variables (when using secret)" + varType: |- + list(object({ + name = string + secret = set(object({ + secret_name = string + items = map(string) + })) + })) + defaultValue: [] + outputs: + - name: domain_map_id + description: Unique Identifier for the created domain map + - name: domain_map_status + description: Status of Domain mapping + - name: location + description: Location in which the Cloud Run service was created + - name: project_id + description: Google Cloud project in which the service was created + - name: revision + description: Deployed revision for the service + - name: service_id + description: Unique Identifier for the created service + - name: service_name + description: Name of the created service + - name: service_status + description: Status of the created service + - name: service_url + description: The URL on which the deployed service is available + - name: verified_domain_name + description: List of Custom Domain Name + requirements: + roles: + - level: Project + roles: + - roles/accesscontextmanager.policyAdmin + - roles/orgpolicy.policyAdmin + - level: Project + roles: + - roles/owner + - level: Project + roles: + - roles/resourcemanager.folderAdmin + - roles/resourcemanager.projectCreator + - roles/resourcemanager.projectDeleter + services: + - cloudresourcemanager.googleapis.com + - storage-api.googleapis.com + - serviceusage.googleapis.com + - run.googleapis.com + - cloudkms.googleapis.com + - iam.googleapis.com + - accesscontextmanager.googleapis.com + - cloudbilling.googleapis.com diff --git a/modules/job-exec/README.md b/modules/job-exec/README.md index 2dc00e74..a9e0e353 100644 --- a/modules/job-exec/README.md +++ b/modules/job-exec/README.md @@ -15,8 +15,8 @@ This module was deploys a Cloud Run Job run and executes it. Basic usage of this module is as follows: ```hcl -module "cloud_run_core" { - source = "GoogleCloudPlatform/cloud-run/google//modules/secure-cloud-run-core" +module "job-exec" { + source = "GoogleCloudPlatform/cloud-run/google//modules/job-exec" version = "~> 0.3.0" project_id = var.project_id diff --git a/modules/job-exec/metadata.yaml b/modules/job-exec/metadata.yaml new file mode 100644 index 00000000..c1525efa --- /dev/null +++ b/modules/job-exec/metadata.yaml @@ -0,0 +1,183 @@ +# 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. + +apiVersion: blueprints.cloud.google.com/v1alpha1 +kind: BlueprintMetadata +metadata: + name: terraform-google-cloud-run-job-exec + annotations: + config.kubernetes.io/local-config: "true" +spec: + info: + title: Cloud Run Job + source: + repo: https://github.com/GoogleCloudPlatform/terraform-google-cloud-run.git + sourceType: git + dir: /modules/job-exec + version: 0.11.0 + actuationTool: + flavor: Terraform + version: ">= 0.13" + description: {} + content: + examples: + - name: cloud_run_vpc_connector + location: examples/cloud_run_vpc_connector + - name: secure_cloud_run + location: examples/secure_cloud_run + - name: secure_cloud_run_standalone + location: examples/secure_cloud_run_standalone + - name: simple_cloud_run + location: examples/simple_cloud_run + - name: simple_cloud_run_with_cmek + location: examples/simple_cloud_run_with_cmek + - name: simple_job_exec + location: examples/simple_job_exec + - name: v2 + location: examples/v2 + interfaces: + variables: + - name: argument + description: Arguments passed to the ENTRYPOINT command, include these only if image entrypoint needs arguments + varType: list(string) + defaultValue: [] + - name: container_command + description: Leave blank to use the ENTRYPOINT command defined in the container image, include these only if image entrypoint should be overwritten + varType: list(string) + defaultValue: [] + - name: env_secret_vars + description: Environment variables (Secret Manager) + varType: |- + list(object({ + name = string + value_source = set(object({ + secret_key_ref = object({ + secret = string + version = optional(string, "latest") + }) + })) + })) + defaultValue: [] + - name: env_vars + description: Environment variables (cleartext) + varType: |- + list(object({ + value = string + name = string + })) + defaultValue: [] + - name: exec + description: Whether to execute job after creation + varType: bool + defaultValue: false + - name: image + description: GCR hosted image URL to deploy + varType: string + required: true + - name: labels + description: A set of key/value label pairs to assign to the Cloud Run job. + varType: map(string) + defaultValue: {} + - name: launch_stage + description: The launch stage. (see https://cloud.google.com/products#product-launch-stages). Defaults to GA. + varType: string + defaultValue: "" + - name: limits + description: Resource limits to the container + varType: |- + object({ + cpu = optional(string) + memory = optional(string) + }) + - name: location + description: Cloud Run job deployment location + varType: string + required: true + - name: max_retries + description: Number of retries allowed per Task, before marking this Task failed. + varType: number + - name: name + description: The name of the Cloud Run job to create + varType: string + required: true + - name: parallelism + description: Specifies the maximum desired number of tasks the execution should run at given time. Must be <= taskCount. + varType: number + - name: project_id + description: The project ID to deploy to + varType: string + required: true + - name: service_account_email + description: Service Account email needed for the job + varType: string + defaultValue: "" + - name: task_count + description: Specifies the desired number of tasks the execution should run. + varType: number + - name: timeout + description: Max allowed time duration the Task may be active before the system will actively try to mark it failed and kill associated containers. + varType: string + defaultValue: 600s + - name: volume_mounts + description: Volume to mount into the container's filesystem. + varType: |- + list(object({ + name = string + mount_path = string + })) + defaultValue: [] + - name: volumes + description: A list of Volumes to make available to containers. + varType: |- + list(object({ + name = string + cloud_sql_instance = object({ + instances = set(string) + }) + })) + defaultValue: [] + - name: vpc_access + description: VPC Access configuration to use for this Task. + varType: |- + list(object({ + connector = string + egress = string + })) + defaultValue: [] + outputs: + - name: id + description: Cloud Run Job ID + requirements: + roles: + - level: Project + roles: + - roles/owner + - level: Project + roles: + - roles/resourcemanager.folderAdmin + - roles/resourcemanager.projectCreator + - roles/resourcemanager.projectDeleter + - level: Project + roles: + - roles/accesscontextmanager.policyAdmin + - roles/orgpolicy.policyAdmin + services: + - cloudresourcemanager.googleapis.com + - storage-api.googleapis.com + - serviceusage.googleapis.com + - run.googleapis.com + - cloudkms.googleapis.com + - iam.googleapis.com + - accesscontextmanager.googleapis.com + - cloudbilling.googleapis.com diff --git a/modules/secure-cloud-run-core/metadata.yaml b/modules/secure-cloud-run-core/metadata.yaml new file mode 100644 index 00000000..c98a3ae8 --- /dev/null +++ b/modules/secure-cloud-run-core/metadata.yaml @@ -0,0 +1,319 @@ +# 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. + +apiVersion: blueprints.cloud.google.com/v1alpha1 +kind: BlueprintMetadata +metadata: + name: terraform-google-cloud-run-secure-cloud-run-core + annotations: + config.kubernetes.io/local-config: "true" +spec: + info: + title: Secure Cloud Run Core + source: + repo: https://github.com/GoogleCloudPlatform/terraform-google-cloud-run.git + sourceType: git + dir: /modules/secure-cloud-run-core + version: 0.11.0 + actuationTool: + flavor: Terraform + version: ">= 0.13" + description: {} + content: + examples: + - name: cloud_run_vpc_connector + location: examples/cloud_run_vpc_connector + - name: secure_cloud_run + location: examples/secure_cloud_run + - name: secure_cloud_run_standalone + location: examples/secure_cloud_run_standalone + - name: simple_cloud_run + location: examples/simple_cloud_run + - name: simple_cloud_run_with_cmek + location: examples/simple_cloud_run_with_cmek + - name: simple_job_exec + location: examples/simple_job_exec + - name: v2 + location: examples/v2 + interfaces: + variables: + - name: argument + description: Arguments passed to the ENTRYPOINT command, include these only if image entrypoint needs arguments. + varType: list(string) + defaultValue: [] + - name: certificate_mode + description: The mode of the certificate (NONE or AUTOMATIC). + varType: string + defaultValue: NONE + - name: cloud_armor_policies_name + description: Cloud Armor policy name already created in the project. If `create_cloud_armor_policies` is `false`, this variable must be provided, If `create_cloud_armor_policies` is `true`, this variable will be ignored. + varType: string + - name: cloud_run_sa + description: Service account to be used on Cloud Run. + varType: string + required: true + - name: container_command + description: Leave blank to use the ENTRYPOINT command defined in the container image, include these only if image entrypoint should be overwritten. + varType: list(string) + defaultValue: [] + - name: container_concurrency + description: Concurrent request limits to the service. + varType: number + - name: create_cloud_armor_policies + description: When `true`, the terraform will create the Cloud Armor policies. When `false`, the user must provide their own Cloud Armor name in `cloud_armor_policies_name`. + varType: bool + defaultValue: true + - name: default_rules + description: Default rule for Cloud Armor. + varType: |- + map(object({ + action = string + priority = string + versioned_expr = string + src_ip_ranges = list(string) + description = string + })) + defaultValue: + default_rule: + action: allow + description: Default allow all rule + priority: "2147483647" + src_ip_ranges: + - "*" + versioned_expr: SRC_IPS_V1 + - name: domain_map_annotations + description: Annotations to the domain map. + varType: map(string) + defaultValue: {} + - name: domain_map_labels + description: A set of key/value label pairs to assign to the Domain mapping. + varType: map(string) + defaultValue: {} + - name: encryption_key + description: CMEK encryption key self-link expected in the format projects/PROJECT/locations/LOCATION/keyRings/KEY-RING/cryptoKeys/CRYPTO-KEY. + varType: string + required: true + - name: env_vars + description: Environment variables. + varType: |- + list(object({ + value = string + name = string + })) + defaultValue: [] + - name: force_override + description: Option to force override existing mapping. + varType: bool + defaultValue: false + - name: generate_revision_name + description: Option to enable revision name generation. + varType: bool + defaultValue: true + - name: image + description: GAR hosted image URL to deploy. + varType: string + required: true + - name: lb_name + description: Name for load balancer and associated resources. + varType: string + defaultValue: tf-cr-lb + - name: limits + description: Resource limits to the container. + varType: map(string) + - name: location + description: The location where resources are going to be deployed. + varType: string + required: true + - name: max_scale_instances + description: Sets the maximum number of container instances needed to handle all incoming requests or events from each revison from Cloud Run. For more information, access this [documentation](https://cloud.google.com/run/docs/about-instance-autoscaling). + varType: number + defaultValue: 2 + - name: members + description: Users/SAs to be given invoker access to the service with the prefix `serviceAccount:' for SAs and `user:` for users. + varType: list(string) + defaultValue: [] + - name: min_scale_instances + description: Sets the minimum number of container instances needed to handle all incoming requests or events from each revison from Cloud Run. For more information, access this [documentation](https://cloud.google.com/run/docs/about-instance-autoscaling). + varType: number + defaultValue: 1 + - name: owasp_rules + description: These are additional Cloud Armor rules for SQLi, XSS, LFI, RCE, RFI, Scannerdetection, Protocolattack and Sessionfixation (requires Cloud Armor default_rule). + varType: |- + map(object({ + action = string + priority = string + expression = string + })) + defaultValue: + rule_canary: + action: deny(403) + expression: evaluatePreconfiguredExpr('rce-v33-stable') + priority: "1003" + rule_lfi: + action: deny(403) + expression: evaluatePreconfiguredExpr('lfi-v33-stable') + priority: "1002" + rule_protocolattack: + action: deny(403) + expression: evaluatePreconfiguredExpr('protocolattack-v33-stable') + priority: "1006" + rule_rfi: + action: deny(403) + expression: evaluatePreconfiguredExpr('rfi-v33-stable') + priority: "1004" + rule_scannerdetection: + action: deny(403) + expression: evaluatePreconfiguredExpr('scannerdetection-v33-stable') + priority: "1005" + rule_sessionfixation: + action: deny(403) + expression: evaluatePreconfiguredExpr('sessionfixation-v33-stable') + priority: "1007" + rule_sqli: + action: deny(403) + expression: evaluatePreconfiguredExpr('sqli-v33-stable') + priority: "1000" + rule_xss: + action: deny(403) + expression: evaluatePreconfiguredExpr('xss-v33-stable') + priority: "1001" + - name: ports + description: Port which the container listens to (http1 or h2c). + varType: |- + object({ + name = string + port = number + }) + defaultValue: + name: http1 + port: 8080 + - name: project_id + description: The project where cloud run is going to be deployed. + varType: string + required: true + - name: region + description: Location for load balancer and Cloud Run resources. + varType: string + required: true + - name: requests + description: Resource requests to the container. + varType: map(string) + defaultValue: {} + - name: service_labels + description: A set of key/value label pairs to assign to the service. + varType: map(string) + defaultValue: {} + - name: service_name + description: The name of the Cloud Run service to create. + varType: string + required: true + - name: ssl_certificates + description: A object with a list of domains to auto-generate SSL certificates or a list of SSL Certificates self-links in the pattern `projects//global/sslCertificates/` to be used by Load Balancer. + varType: |- + object({ + ssl_certificates_self_links = list(string) + generate_certificates_for_domains = list(string) + }) + required: true + - name: template_labels + description: A set of key/value label pairs to assign to the container metadata. + varType: map(string) + defaultValue: {} + - name: timeout_seconds + description: Timeout for each request. + varType: number + defaultValue: 120 + - name: traffic_split + description: Managing traffic routing to the service. + varType: |- + list(object({ + latest_revision = bool + percent = number + revision_name = string + tag = string + })) + defaultValue: + - latest_revision: true + percent: 100 + revision_name: v1-0-0 + tag: null + - name: verified_domain_name + description: List of custom Domain Name. + varType: list(string) + required: true + - name: volume_mounts + description: "[Beta] Volume Mounts to be attached to the container (when using secret)." + varType: |- + list(object({ + mount_path = string + name = string + })) + defaultValue: [] + - name: volumes + description: "[Beta] Volumes needed for environment variables (when using secret)." + varType: |- + list(object({ + name = string + secret = set(object({ + secret_name = string + items = map(string) + })) + })) + defaultValue: [] + - name: vpc_connector_id + description: VPC Connector id in the format projects/PROJECT/locations/LOCATION/connectors/NAME. + varType: string + required: true + - name: vpc_egress_value + description: Sets VPC Egress firewall rule. Supported values are all-traffic, all (deprecated), and private-ranges-only. all-traffic and all provide the same functionality. all is deprecated but will continue to be supported. Prefer all-traffic. + varType: string + defaultValue: private-ranges-only + outputs: + - name: domain_map_id + description: Unique Identifier for the created domain map. + - name: domain_map_status + description: Status of Domain mapping. + - name: load_balancer_ip + description: IP Address used by Load Balancer. + - name: revision + description: Deployed revision for the service. + - name: service_id + description: Unique Identifier for the created service. + - name: service_status + description: Status of the created service. + - name: service_url + description: The URL on which the deployed service is available. + requirements: + roles: + - level: Project + roles: + - roles/owner + - level: Project + roles: + - roles/resourcemanager.folderAdmin + - roles/resourcemanager.projectCreator + - roles/resourcemanager.projectDeleter + - level: Project + roles: + - roles/accesscontextmanager.policyAdmin + - roles/orgpolicy.policyAdmin + services: + - cloudresourcemanager.googleapis.com + - storage-api.googleapis.com + - serviceusage.googleapis.com + - run.googleapis.com + - cloudkms.googleapis.com + - iam.googleapis.com + - accesscontextmanager.googleapis.com + - cloudbilling.googleapis.com diff --git a/modules/secure-cloud-run-security/metadata.yaml b/modules/secure-cloud-run-security/metadata.yaml new file mode 100644 index 00000000..f6f101f9 --- /dev/null +++ b/modules/secure-cloud-run-security/metadata.yaml @@ -0,0 +1,146 @@ +# 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. + +apiVersion: blueprints.cloud.google.com/v1alpha1 +kind: BlueprintMetadata +metadata: + name: terraform-google-cloud-run-secure-cloud-run-security + annotations: + config.kubernetes.io/local-config: "true" +spec: + info: + title: Secure Cloud Run Security + source: + repo: https://github.com/GoogleCloudPlatform/terraform-google-cloud-run.git + sourceType: git + dir: /modules/secure-cloud-run-security + version: 0.11.0 + actuationTool: + flavor: Terraform + version: ">= 0.13" + description: {} + content: + examples: + - name: cloud_run_vpc_connector + location: examples/cloud_run_vpc_connector + - name: secure_cloud_run + location: examples/secure_cloud_run + - name: secure_cloud_run_standalone + location: examples/secure_cloud_run_standalone + - name: simple_cloud_run + location: examples/simple_cloud_run + - name: simple_cloud_run_with_cmek + location: examples/simple_cloud_run_with_cmek + - name: simple_job_exec + location: examples/simple_job_exec + - name: v2 + location: examples/v2 + interfaces: + variables: + - name: decrypters + description: List of comma-separated owners for each key declared in set_decrypters_for. + varType: list(string) + defaultValue: [] + - name: encrypters + description: List of comma-separated owners for each key declared in set_encrypters_for. + varType: list(string) + defaultValue: [] + - name: folder_id + description: The folder ID to apply the policy to. + varType: string + defaultValue: "" + - name: groups + description: " Groups which will have roles assigned.\n The Serverless Administrators email group which the following roles will be added: Cloud Run Admin, Compute Network Viewer and Compute Network User.\n The Serverless Security Administrators email group which the following roles will be added: Cloud Run Viewer, Cloud KMS Viewer and Artifact Registry Reader.\n The Cloud Run Developer email group which the following roles will be added: Cloud Run Developer, Artifact Registry Writer and Cloud KMS CryptoKey Encrypter.\n The Cloud Run User email group which the following roles will be added: Cloud Run Invoker.\n" + varType: |- + object({ + group_serverless_administrator = optional(string, null) + group_serverless_security_administrator = optional(string, null) + group_cloud_run_developer = optional(string, null) + group_cloud_run_user = optional(string, null) + }) + defaultValue: {} + - name: key_name + description: Key name. + varType: string + required: true + - name: key_protection_level + description: "The protection level to use when creating a version based on this template. Possible values: [\"SOFTWARE\", \"HSM\"]" + varType: string + defaultValue: HSM + - name: key_rotation_period + description: Period of key rotation in seconds. + varType: string + defaultValue: 2592000s + - name: keyring_name + description: Keyring name. + varType: string + required: true + - name: kms_project_id + description: The project where KMS will be created. + varType: string + required: true + - name: location + description: The location where resources are going to be deployed. + varType: string + required: true + - name: organization_id + description: The organization ID to apply the policy to. + varType: string + defaultValue: "" + - name: owners + description: List of comma-separated owners for each key declared in set_owners_for. + varType: list(string) + defaultValue: [] + - name: policy_for + description: "Policy Root: set one of the following values to determine where the policy is applied. Possible values: [\"project\", \"folder\", \"organization\"]." + varType: string + defaultValue: project + - name: prevent_destroy + description: Set the prevent_destroy lifecycle attribute on keys.. + varType: bool + defaultValue: true + - name: serverless_project_id + description: The project where Cloud Run is going to be deployed. + varType: string + required: true + outputs: + - name: key_self_link + description: Key self link. + - name: keyring_resource + description: Keyring resource. + - name: keyring_self_link + description: Self link of the keyring. + requirements: + roles: + - level: Project + roles: + - roles/accesscontextmanager.policyAdmin + - roles/orgpolicy.policyAdmin + - level: Project + roles: + - roles/owner + - level: Project + roles: + - roles/resourcemanager.folderAdmin + - roles/resourcemanager.projectCreator + - roles/resourcemanager.projectDeleter + services: + - cloudresourcemanager.googleapis.com + - storage-api.googleapis.com + - serviceusage.googleapis.com + - run.googleapis.com + - cloudkms.googleapis.com + - iam.googleapis.com + - accesscontextmanager.googleapis.com + - cloudbilling.googleapis.com diff --git a/modules/secure-cloud-run/metadata.yaml b/modules/secure-cloud-run/metadata.yaml new file mode 100644 index 00000000..50cff252 --- /dev/null +++ b/modules/secure-cloud-run/metadata.yaml @@ -0,0 +1,263 @@ +# 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. + +apiVersion: blueprints.cloud.google.com/v1alpha1 +kind: BlueprintMetadata +metadata: + name: terraform-google-cloud-run-secure-cloud-run + annotations: + config.kubernetes.io/local-config: "true" +spec: + info: + title: Secure Cloud Run + source: + repo: https://github.com/GoogleCloudPlatform/terraform-google-cloud-run.git + sourceType: git + dir: /modules/secure-cloud-run + version: 0.11.0 + actuationTool: + flavor: Terraform + version: ">= 0.13" + description: {} + content: + examples: + - name: cloud_run_vpc_connector + location: examples/cloud_run_vpc_connector + - name: secure_cloud_run + location: examples/secure_cloud_run + - name: secure_cloud_run_standalone + location: examples/secure_cloud_run_standalone + - name: simple_cloud_run + location: examples/simple_cloud_run + - name: simple_cloud_run_with_cmek + location: examples/simple_cloud_run_with_cmek + - name: simple_job_exec + location: examples/simple_job_exec + - name: v2 + location: examples/v2 + interfaces: + variables: + - name: artifact_registry_repository_location + description: Artifact Registry Repository location to grant serverless identity viewer role. + varType: string + - name: artifact_registry_repository_name + description: Artifact Registry Repository name to grant serverless identity viewer role + varType: string + - name: artifact_registry_repository_project_id + description: Artifact Registry Repository Project ID to grant serverless identity viewer role. + varType: string + - name: cloud_armor_policies_name + description: Cloud Armor policy name already created in the project. If `create_cloud_armor_policies` is `false`, this variable must be provided, If `create_cloud_armor_policies` is `true`, this variable will be ignored. + varType: string + - name: cloud_run_sa + description: Service account to be used on Cloud Run. + varType: string + required: true + - name: connector_name + description: The name for the connector to be created. + varType: string + defaultValue: serverless-vpc-connector + - name: create_cloud_armor_policies + description: When `true`, the terraform will create the Cloud Armor policies. When `false`, the user must provide their own Cloud Armor name in `cloud_armor_policies_name`. + varType: bool + defaultValue: true + - name: create_subnet + description: The subnet will be created with the subnet_name variable if true. When false, it will use the subnet_name for the subnet. + varType: bool + defaultValue: true + - name: env_vars + description: Environment variables (cleartext) + varType: |- + list(object({ + value = string + name = string + })) + defaultValue: [] + - name: folder_id + description: The folder ID to apply the policy to. + varType: string + defaultValue: "" + - name: grant_artifact_register_reader + description: When true it will grant permission to read an image from your artifact registry. When true, you must provide `artifact_registry_repository_project_id`, `artifact_registry_repository_location` and `artifact_registry_repository_name`. + varType: bool + defaultValue: false + - name: groups + description: " Groups which will have roles assigned.\n The Serverless Administrators email group which the following roles will be added: Cloud Run Admin, Compute Network Viewer and Compute Network User.\n The Serverless Security Administrators email group which the following roles will be added: Cloud Run Viewer, Cloud KMS Viewer and Artifact Registry Reader.\n The Cloud Run Developer email group which the following roles will be added: Cloud Run Developer, Artifact Registry Writer and Cloud KMS CryptoKey Encrypter.\n The Cloud Run User email group which the following roles will be added: Cloud Run Invoker.\n" + varType: |- + object({ + group_serverless_administrator = optional(string, null) + group_serverless_security_administrator = optional(string, null) + group_cloud_run_developer = optional(string, null) + group_cloud_run_user = optional(string, null) + }) + defaultValue: {} + - name: image + description: Image url to be deployed on Cloud Run. + varType: string + required: true + - name: ip_cidr_range + description: The range of internal addresses that are owned by the subnetwork and which is going to be used by VPC Connector. For example, 10.0.0.0/28 or 192.168.0.0/28. Ranges must be unique and non-overlapping within a network. Only IPv4 is supported. + varType: string + required: true + - name: key_name + description: The name of KMS Key to be created and used in Cloud Run. + varType: string + defaultValue: cloud-run-kms-key + - name: key_protection_level + description: "The protection level to use when creating a version based on this template. Possible values: [\"SOFTWARE\", \"HSM\"]" + varType: string + defaultValue: HSM + - name: key_rotation_period + description: Period of key rotation in seconds. + varType: string + defaultValue: 2592000s + - name: keyring_name + description: Keyring name. + varType: string + defaultValue: cloud-run-kms-keyring + - name: kms_project_id + description: The project where KMS will be created. + varType: string + required: true + - name: location + description: The location where resources are going to be deployed. + varType: string + required: true + - name: max_scale_instances + description: Sets the maximum number of container instances needed to handle all incoming requests or events from each revison from Cloud Run. For more information, access this [documentation](https://cloud.google.com/run/docs/about-instance-autoscaling). + varType: number + defaultValue: 2 + - name: members + description: Users/SAs to be given invoker access to the service with the prefix `serviceAccount:' for SAs and `user:` for users. + varType: list(string) + defaultValue: [] + - name: min_scale_instances + description: Sets the minimum number of container instances needed to handle all incoming requests or events from each revison from Cloud Run. For more information, access this [documentation](https://cloud.google.com/run/docs/about-instance-autoscaling). + varType: number + defaultValue: 1 + - name: organization_id + description: The organization ID to apply the policy to. + varType: string + defaultValue: "" + - name: policy_for + description: "Policy Root: set one of the following values to determine where the policy is applied. Possible values: [\"project\", \"folder\", \"organization\"]." + varType: string + defaultValue: project + - name: prevent_destroy + description: Set the `prevent_destroy` lifecycle attribute on the Cloud KMS key. + varType: bool + defaultValue: true + - name: region + description: Location for load balancer and Cloud Run resources. + varType: string + required: true + - name: resource_names_suffix + description: A suffix to concat in the end of the network resources names being created. + varType: string + - name: serverless_project_id + description: The project to deploy the cloud run service. + varType: string + required: true + - name: service_name + description: Shared VPC name. + varType: string + required: true + - name: shared_vpc_name + description: Shared VPC name which is going to be re-used to create Serverless Connector. + varType: string + required: true + - name: ssl_certificates + description: A object with a list of domains to auto-generate SSL certificates or a list of SSL Certificates self-links in the pattern `projects//global/sslCertificates/` to be used by Load Balancer. + varType: |- + object({ + ssl_certificates_self_links = list(string) + generate_certificates_for_domains = list(string) + }) + required: true + - name: subnet_name + description: Subnet name to be re-used to create Serverless Connector. + varType: string + - name: verified_domain_name + description: List of Custom Domain Name + varType: list(string) + defaultValue: [] + - name: volumes + description: "[Beta] Volumes needed for environment variables (when using secret)." + varType: |- + list(object({ + name = string + secret = set(object({ + secret_name = string + items = map(string) + })) + })) + defaultValue: [] + - name: vpc_egress_value + description: Sets VPC Egress firewall rule. Supported values are all-traffic, all (deprecated), and private-ranges-only. all-traffic and all provide the same functionality. all is deprecated but will continue to be supported. Prefer all-traffic. + varType: string + defaultValue: private-ranges-only + - name: vpc_project_id + description: The host project for the shared vpc. + varType: string + required: true + outputs: + - name: cloud_services_sa + description: Service Account for Cloud Run Service. + - name: connector_id + description: VPC serverless connector ID. + - name: domain_map_id + description: Unique Identifier for the created domain map. + - name: domain_map_status + description: Status of Domain mapping. + - name: gca_vpcaccess_sa + description: Service Account for VPC Access. + - name: key_self_link + description: Name of the Cloud KMS crypto key. + - name: keyring_self_link + description: Name of the Cloud KMS keyring. + - name: load_balancer_ip + description: IP Address used by Load Balancer. + - name: revision + description: Deployed revision for the service. + - name: run_identity_services_sa + description: Service Identity to run services. + - name: service_id + description: ID of the created service. + - name: service_status + description: Status of the created service. + - name: service_url + description: Url of the created service. + requirements: + roles: + - level: Project + roles: + - roles/resourcemanager.folderAdmin + - roles/resourcemanager.projectCreator + - roles/resourcemanager.projectDeleter + - level: Project + roles: + - roles/accesscontextmanager.policyAdmin + - roles/orgpolicy.policyAdmin + - level: Project + roles: + - roles/owner + services: + - cloudresourcemanager.googleapis.com + - storage-api.googleapis.com + - serviceusage.googleapis.com + - run.googleapis.com + - cloudkms.googleapis.com + - iam.googleapis.com + - accesscontextmanager.googleapis.com + - cloudbilling.googleapis.com diff --git a/modules/secure-serverless-harness/metadata.yaml b/modules/secure-serverless-harness/metadata.yaml new file mode 100644 index 00000000..da0c5d4b --- /dev/null +++ b/modules/secure-serverless-harness/metadata.yaml @@ -0,0 +1,283 @@ +# 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. + +apiVersion: blueprints.cloud.google.com/v1alpha1 +kind: BlueprintMetadata +metadata: + name: terraform-google-cloud-run-secure-serverless-harness + annotations: + config.kubernetes.io/local-config: "true" +spec: + info: + title: Secure Serverless Harness + source: + repo: https://github.com/GoogleCloudPlatform/terraform-google-cloud-run.git + sourceType: git + dir: /modules/secure-serverless-harness + version: 0.11.0 + actuationTool: + flavor: Terraform + version: ">= 0.13" + description: {} + content: + examples: + - name: cloud_run_vpc_connector + location: examples/cloud_run_vpc_connector + - name: secure_cloud_run + location: examples/secure_cloud_run + - name: secure_cloud_run_standalone + location: examples/secure_cloud_run_standalone + - name: simple_cloud_run + location: examples/simple_cloud_run + - name: simple_cloud_run_with_cmek + location: examples/simple_cloud_run_with_cmek + - name: simple_job_exec + location: examples/simple_job_exec + - name: v2 + location: examples/v2 + interfaces: + variables: + - name: access_context_manager_policy_id + description: The ID of the default Access Context Manager policy. Can be obtained by running `gcloud access-context-manager policies list --organization YOUR_ORGANIZATION_ID --format="value(name)"`. + varType: number + - name: access_level_members + description: The list of additional members who will be in the access level. + varType: list(string) + required: true + - name: artifact_registry_repository_description + description: The description of the Artifact Registry Repository to be created. + varType: string + defaultValue: Secure Cloud Run Artifact Registry Repository + - name: artifact_registry_repository_format + description: The format of the Artifact Registry Repository to be created. + varType: string + defaultValue: DOCKER + - name: artifact_registry_repository_name + description: The name of the Artifact Registry Repository to be created. + varType: string + required: true + - name: base_serverless_api + description: This variable will enable Cloud Function or Cloud Run specific resources. Cloud Run API will be used for the terraform-google-cloud-run repository while Cloud Function API will be used in the terraform-google-cloud-functions repository. It supports only run.googleapis.com or cloudfunctions.googleapis.com + varType: string + required: true + - name: billing_account + description: The ID of the billing account to associate this project with. + varType: string + required: true + - name: create_access_context_manager_access_policy + description: Defines if Access Context Manager will be created by Terraform. + varType: bool + defaultValue: false + - name: decrypters + description: List of comma-separated owners for each key declared in set_decrypters_for. + varType: list(string) + defaultValue: [] + - name: disable_services_on_destroy + description: Whether project services will be disabled when the resources are destroyed + varType: bool + defaultValue: false + - name: dns_enable_inbound_forwarding + description: Toggle inbound query forwarding for VPC DNS. + varType: bool + defaultValue: true + - name: dns_enable_logging + description: Toggle DNS logging for VPC DNS. + varType: bool + defaultValue: true + - name: egress_policies + description: |- + A list of all [egress policies](https://cloud.google.com/vpc-service-controls/docs/ingress-egress-rules#egress-rules-reference), each list object has a `from` and `to` value that describes egress_from and egress_to. + + Example: `[{ from={ identities=[], identity_type="ID_TYPE" }, to={ resources=[], operations={ "SRV_NAME"={ OP_TYPE=[] }}}}]` + + Valid Values: + `ID_TYPE` = `null` or `IDENTITY_TYPE_UNSPECIFIED` (only allow identities from list); `ANY_IDENTITY`; `ANY_USER_ACCOUNT`; `ANY_SERVICE_ACCOUNT` + `SRV_NAME` = "`*`" (allow all services) or [Specific Services](https://cloud.google.com/vpc-service-controls/docs/supported-products#supported_products) + `OP_TYPE` = [methods](https://cloud.google.com/vpc-service-controls/docs/supported-method-restrictions) or [permissions](https://cloud.google.com/vpc-service-controls/docs/supported-method-restrictions). + varType: |- + list(object({ + from = any + to = any + })) + defaultValue: [] + - name: encrypters + description: List of comma-separated owners for each key declared in set_encrypters_for. + varType: list(string) + defaultValue: [] + - name: ingress_policies + description: |- + A list of all [ingress policies](https://cloud.google.com/vpc-service-controls/docs/ingress-egress-rules#ingress-rules-reference), each list object has a `from` and `to` value that describes ingress_from and ingress_to. + + Example: `[{ from={ sources={ resources=[], access_levels=[] }, identities=[], identity_type="ID_TYPE" }, to={ resources=[], operations={ "SRV_NAME"={ OP_TYPE=[] }}}}]` + + Valid Values: + `ID_TYPE` = `null` or `IDENTITY_TYPE_UNSPECIFIED` (only allow identities from list); `ANY_IDENTITY`; `ANY_USER_ACCOUNT`; `ANY_SERVICE_ACCOUNT` + `SRV_NAME` = "`*`" (allow all services) or [Specific Services](https://cloud.google.com/vpc-service-controls/docs/supported-products#supported_products) + `OP_TYPE` = [methods](https://cloud.google.com/vpc-service-controls/docs/supported-method-restrictions) or [permissions](https://cloud.google.com/vpc-service-controls/docs/supported-method-restrictions). + varType: |- + list(object({ + from = any + to = any + })) + defaultValue: [] + - name: key_name + description: Key name. + varType: string + required: true + - name: key_protection_level + description: "The protection level to use when creating a version based on this template. Possible values: [\"SOFTWARE\", \"HSM\"]." + varType: string + defaultValue: HSM + - name: key_rotation_period + description: Period of key rotation in seconds. Default value is equivalent to 30 days. + varType: string + defaultValue: 2592000s + - name: keyring_name + description: Keyring name. + varType: string + required: true + - name: location + description: The location where resources are going to be deployed. + varType: string + required: true + - name: network_project_extra_apis + description: The extra APIs to be enabled during network project creation. + varType: list(string) + defaultValue: [] + - name: network_project_name + description: The name to give the shared vpc project. + varType: string + defaultValue: "" + - name: org_id + description: The organization ID. + varType: string + required: true + - name: owners + description: List of comma-separated owners for each key declared in set_owners_for. + varType: list(string) + defaultValue: [] + - name: parent_folder_id + description: The ID of a folder to host the infrastructure created in this module. + varType: string + defaultValue: "" + - name: prevent_destroy + description: Set the prevent_destroy lifecycle attribute on keys. + varType: bool + defaultValue: true + - name: private_service_connect_ip + description: The internal IP to be used for the private service connect. + varType: string + required: true + - name: region + description: The region in which the subnetwork will be created. + varType: string + required: true + - name: security_project_extra_apis + description: The extra APIs to be enabled during security project creation. + varType: list(string) + defaultValue: [] + - name: security_project_name + description: The name to give the security project. + varType: string + required: true + - name: serverless_folder_suffix + description: The suffix to be concat in the Serverless folder name fldr-serverless-. + varType: string + defaultValue: "" + - name: serverless_project_extra_apis + description: The extra APIs to be enabled during serverless projects creation. + varType: map(list(string)) + defaultValue: {} + - name: serverless_project_names + description: The name to give the Cloud Serverless project. + varType: list(string) + required: true + - name: service_account_project_roles + description: Common roles to apply to the Cloud Serverless service account in the serverless project. + varType: map(list(string)) + defaultValue: {} + - name: subnet_ip + description: The CDIR IP range of the subnetwork. + varType: string + required: true + - name: time_to_wait_vpc_sc_propagation + description: The time to wait VPC-SC propagation when applying and destroying. + varType: string + defaultValue: 180s + - name: use_shared_vpc + description: Defines if the network created will be a single or shared vpc. + varType: bool + defaultValue: false + - name: vpc_name + description: The name of the network. + varType: string + required: true + outputs: + - name: access_context_manager_policy_id + description: Access Context Manager ID. + - name: artifact_registry_key + description: Artifact Registry KMS Key. + - name: artifact_registry_repository_id + description: The Artifact Registry Repository full identifier where the images should be stored. + - name: artifact_registry_repository_name + description: The Artifact Registry Repository last part of the repository name where the images should be stored. + - name: cloud_serverless_service_identity_email + description: The Cloud Run Service Identity email. + - name: network_project_id + description: Project ID of the project created to host the Serverless Network. + - name: restricted_access_level_name + description: Access level name. + - name: restricted_access_level_name_id + description: Access level name id. + - name: restricted_service_perimeter_name + description: Service Perimeter name. + - name: security_project_id + description: Project ID of the project created for KMS and Artifact Register. + - name: security_project_number + description: Project number of the project created for KMS and Artifact Register. + - name: serverless_folder_id + description: The folder created to allocate Serverless infra. + - name: serverless_project_ids + description: Project ID of the projects created to deploy Serverless application. + - name: serverless_project_numbers + description: Project number of the projects created to deploy Serverless applications. + - name: service_account_email + description: The email of the Service Account created to be used by Cloud Serverless. + - name: service_subnet + description: The sub-network name created in harness. + - name: service_vpc + description: The network created for Cloud Serverless. + requirements: + roles: + - level: Project + roles: + - roles/owner + - level: Project + roles: + - roles/resourcemanager.folderAdmin + - roles/resourcemanager.projectCreator + - roles/resourcemanager.projectDeleter + - level: Project + roles: + - roles/accesscontextmanager.policyAdmin + - roles/orgpolicy.policyAdmin + services: + - cloudresourcemanager.googleapis.com + - storage-api.googleapis.com + - serviceusage.googleapis.com + - run.googleapis.com + - cloudkms.googleapis.com + - iam.googleapis.com + - accesscontextmanager.googleapis.com + - cloudbilling.googleapis.com diff --git a/modules/secure-serverless-net/metadata.yaml b/modules/secure-serverless-net/metadata.yaml new file mode 100644 index 00000000..47aa3438 --- /dev/null +++ b/modules/secure-serverless-net/metadata.yaml @@ -0,0 +1,133 @@ +# 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. + +apiVersion: blueprints.cloud.google.com/v1alpha1 +kind: BlueprintMetadata +metadata: + name: terraform-google-cloud-run-secure-serverless-net + annotations: + config.kubernetes.io/local-config: "true" +spec: + info: + title: Secure Serverless Network + source: + repo: https://github.com/GoogleCloudPlatform/terraform-google-cloud-run.git + sourceType: git + dir: /modules/secure-serverless-net + version: 0.11.0 + actuationTool: + flavor: Terraform + version: ">= 0.13" + description: {} + content: + examples: + - name: cloud_run_vpc_connector + location: examples/cloud_run_vpc_connector + - name: secure_cloud_run + location: examples/secure_cloud_run + - name: secure_cloud_run_standalone + location: examples/secure_cloud_run_standalone + - name: simple_cloud_run + location: examples/simple_cloud_run + - name: simple_cloud_run_with_cmek + location: examples/simple_cloud_run_with_cmek + - name: simple_job_exec + location: examples/simple_job_exec + - name: v2 + location: examples/v2 + interfaces: + variables: + - name: connector_name + description: The name of the serverless connector which is going to be created. + varType: string + required: true + - name: connector_on_host_project + description: Connector is going to be created on the host project if true. When false, connector is going to be created on service project. For more information, access [documentation](https://cloud.google.com/run/docs/configuring/connecting-shared-vpc). + varType: bool + defaultValue: false + - name: create_subnet + description: The subnet will be created with the subnet_name variable if true. When false, it will use the subnet_name for the subnet. + varType: bool + defaultValue: true + - name: enable_load_balancer_fw + description: Create the firewall rule for Cloud Run to enable the VPC Connector to access the Load Balancer instance using TCP port 80. Default is true. If using Cloud Function set to false. + varType: bool + defaultValue: true + - name: flow_sampling + description: Sampling rate of VPC flow logs. The value must be in [0,1]. Where 1.0 means all logs, 0.5 mean half of the logs and 0.0 means no logs are reported. + varType: number + defaultValue: 1 + - name: ip_cidr_range + description: The range of internal addresses that are owned by the subnetwork and which is going to be used by VPC Connector. For example, 10.0.0.0/28 or 192.168.0.0/28. Ranges must be unique and non-overlapping within a network. Only IPv4 is supported. + varType: string + required: true + - name: location + description: The location where resources are going to be deployed. + varType: string + required: true + - name: resource_names_suffix + description: A suffix to concat in the end of the resources names. + varType: string + - name: serverless_project_id + description: The project where Secure Serverless is going to be deployed. + varType: string + required: true + - name: serverless_service_identity_email + description: The Service Identity email for the serverless resource (Cloud Run or Cloud Function). + varType: string + required: true + - name: shared_vpc_name + description: Shared VPC name which is going to be used to create Serverless Connector. + varType: string + required: true + - name: subnet_name + description: Subnet name to be re-used to create Serverless Connector. + varType: string + required: true + - name: vpc_project_id + description: The project where shared vpc is. + varType: string + required: true + outputs: + - name: cloud_services_sa + description: Google APIs service agent. + - name: connector_id + description: VPC serverless connector ID. + - name: gca_vpcaccess_sa + description: Google APIs Service Agent for VPC Access. + - name: subnet_name + description: The name of the sub-network used to create VPC Connector. + requirements: + roles: + - level: Project + roles: + - roles/owner + - level: Project + roles: + - roles/resourcemanager.folderAdmin + - roles/resourcemanager.projectCreator + - roles/resourcemanager.projectDeleter + - level: Project + roles: + - roles/accesscontextmanager.policyAdmin + - roles/orgpolicy.policyAdmin + services: + - cloudresourcemanager.googleapis.com + - storage-api.googleapis.com + - serviceusage.googleapis.com + - run.googleapis.com + - cloudkms.googleapis.com + - iam.googleapis.com + - accesscontextmanager.googleapis.com + - cloudbilling.googleapis.com diff --git a/modules/v2/README.md b/modules/v2/README.md new file mode 100644 index 00000000..813431b1 --- /dev/null +++ b/modules/v2/README.md @@ -0,0 +1,86 @@ +# Cloud Run v2 Service + +## Description + +### tagline + +Deploy a Cloud Run Service using v2 API + +### detailed + +This module was deploys a Cloud Run Service and assigns access to the members. + +## Usage + +Basic usage of this module is as follows: + +```hcl +module "cloud_run_core" { + source = "GoogleCloudPlatform/cloud-run/google//modules/v2" + version = "~> 0.11.0" + + project_id = var.project_id + service_name = "hello-world" + location = "us-central1" + containers = { + hello-world = { + container_image = "us-docker.pkg.dev/cloudrun/container/hello" + } + } +} +``` + +Functional examples are included in the +[examples](./examples/) directory. + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| binary\_authorization | Settings for the Binary Authorization feature. |
object({
breakglass_justification = optional(bool) # If present, indicates to use Breakglass using this justification. If useDefault is False, then it must be empty. For more information on breakglass, see https://cloud.google.com/binary-authorization/docs/using-breakglass
use_default = optional(bool) #If True, indicates to use the default project's binary authorization policy. If False, binary authorization will be disabled.
})
| `null` | no | +| client | Arbitrary identifier for the API client and version identifier |
object({
name = optional(string, null)
version = optional(string, null)
})
| `{}` | no | +| containers | Map of container images for the service |
list(object({
container_name = optional(string, null)
container_image = string
working_dir = optional(string, null)
depends_on_container = optional(list(string), null)
container_args = optional(list(string), null)
container_command = optional(list(string), null)
env_vars = optional(map(string), {})
env_secret_vars = optional(map(object({
secret = string
version = string
})), {})
volume_mounts = optional(list(object({
name = string
mount_path = string
})), [])
ports = optional(object({
name = optional(string)
container_port = optional(number)
}), {
name = "http1"
container_port = 8080
})
resources = optional(object({
limits = optional(object({
cpu = optional(string)
memory = optional(string)
}))
cpu_idle = optional(bool, true)
startup_cpu_boost = optional(bool, false)
}), {})
startup_probe = optional(object({
failure_threshold = optional(number, null)
initial_delay_seconds = optional(number, null)
timeout_seconds = optional(number, null)
period_seconds = optional(number, null)
http_get = optional(object({
path = optional(string)
port = optional(string)
http_headers = optional(list(object({
name = string
value = string
})), null)
}), null)
tcp_socket = optional(object({
port = optional(number)
}), null)
grpc = optional(object({
port = optional(number)
service = optional(string)
}), null)
}), null)
liveness_probe = optional(object({
failure_threshold = optional(number, null)
initial_delay_seconds = optional(number, null)
timeout_seconds = optional(number, null)
period_seconds = optional(number, null)
http_get = optional(object({
path = optional(string)
port = optional(string)
http_headers = optional(list(object({
name = string
value = string
})), null)
}), null)
grpc = optional(object({
port = optional(number)
service = optional(string)
}), null)
}), null)
}))
| n/a | yes | +| custom\_audiences | One or more custom audiences that you want this service to support. Specify each custom audience as the full URL in a string. Refer https://cloud.google.com/run/docs/configuring/custom-audiences | `list(string)` | `null` | no | +| description | Cloud Run service description. This field currently has a 512-character limit. | `string` | `null` | no | +| encryption\_key | A reference to a customer managed encryption key (CMEK) to use to encrypt this container image. | `string` | `null` | no | +| execution\_environment | The sandbox environment to host this Revision. | `string` | `"EXECUTION_ENVIRONMENT_GEN2"` | no | +| ingress | Provides the ingress settings for this Service. On output, returns the currently observed ingress settings, or INGRESS\_TRAFFIC\_UNSPECIFIED if no revision is active. | `string` | `"INGRESS_TRAFFIC_ALL"` | no | +| launch\_stage | The launch stage as defined by Google Cloud Platform Launch Stages. Cloud Run supports ALPHA, BETA, and GA. If no value is specified, GA is assumed. | `string` | `"GA"` | no | +| location | Cloud Run service deployment location | `string` | n/a | yes | +| max\_instance\_request\_concurrency | Sets the maximum number of requests that each serving instance can receive | `string` | `null` | no | +| members | Users/SAs to be given invoker access to the service | `list(string)` | `[]` | no | +| project\_id | The project ID to deploy to | `string` | n/a | yes | +| revision | The unique name for the revision. If this field is omitted, it will be automatically generated based on the Service name | `string` | `null` | no | +| service\_account | Email address of the IAM service account associated with the revision of the service | `string` | `null` | no | +| service\_annotations | Unstructured key value map that may be set by external tools to store and arbitrary metadata. They are not queryable and should be preserved when modifying objects. Refer https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#annotations | `map(string)` | `{}` | no | +| service\_labels | Unstructured key value map that can be used to organize and categorize objects. For more information, visit https://cloud.google.com/resource-manager/docs/creating-managing-labels or https://cloud.google.com/run/docs/configuring/labels | `map(string)` | `{}` | no | +| service\_name | The name of the Cloud Run service to create | `string` | n/a | yes | +| service\_scaling | Scaling settings that apply to the whole service |
object({
min_instance_count = optional(number)
})
| `null` | no | +| session\_affinity | Enables session affinity. For more information, go to https://cloud.google.com/run/docs/configuring/session-affinity | `string` | `null` | no | +| template\_annotations | Unstructured key value map that may be set by external tools to store and arbitrary metadata. They are not queryable and should be preserved when modifying objects. Refer https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#annotations | `map(string)` | `{}` | no | +| template\_labels | Unstructured key value map that can be used to organize and categorize objects. For more information, visit https://cloud.google.com/resource-manager/docs/creating-managing-labels or https://cloud.google.com/run/docs/configuring/labels | `map(string)` | `{}` | no | +| template\_scaling | Scaling settings for this Revision. |
object({
min_instance_count = optional(number)
max_instance_count = optional(number)
})
| `{}` | no | +| timeout | Max allowed time for an instance to respond to a request. A duration in seconds with up to nine fractional digits, ending with 's' | `string` | `null` | no | +| traffic | Specifies how to distribute traffic over a collection of Revisions belonging to the Service. If traffic is empty or not provided, defaults to 100% traffic to the latest Ready Revision. |
list(object({
type = optional(string, "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST")
percent = optional(number, 100)
revision = optional(string, null)
tag = optional(string, null)
}))
| `[]` | no | +| volumes | Volumes needed for environment variables (when using secret) |
list(object({
name = string
secret = object({
secret = string
default_mode = optional(string)
items = optional(object({
path = string
version = optional(string)
mode = optional(string)
}))
})
cloud_sql_instance = optional(object({
instances = optional(string)
}))
empty_dir = optional(object({
medium = optional(string)
size_limit = optional(string)
}))
gcs = optional(object({
bucket = string
read_only = optional(string)
}))
nfs = optional(object({
server = string
path = string
read_only = optional(string)
}))
}))
| `[]` | no | +| vpc\_access | VPC Access configuration to use for this Task. For more information, visit https://cloud.google.com/run/docs/configuring/connecting-vpc |
object({
connector = optional(string)
egress = optional(string)
network_interfaces = optional(object({
network = optional(string)
subnetwork = optional(string)
tags = optional(list(string))
}))
})
| `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| creator | Email address of the authenticated creator. | +| effective\_annotations | All of annotations (key/value pairs) present on the resource in GCP, including the annotations configured through Terraform, other clients and services. | +| last\_modifier | Email address of the last authenticated modifier. | +| latest\_created\_revision | Name of the last created revision. See comments in reconciling for additional information on reconciliation process in Cloud Run. | +| latest\_ready\_revision | Name of the latest revision that is serving traffic. See comments in reconciling for additional information on reconciliation process in Cloud Run. | +| location | Location in which the Cloud Run service was created | +| observed\_generation | The generation of this Service currently serving traffic. | +| project\_id | Google Cloud project in which the service was created | +| service\_id | Unique Identifier for the created service with format projects/{{project}}/locations/{{location}}/services/{{name}} | +| service\_name | Name of the created service | +| service\_uri | The main URI in which this Service is serving traffic. | +| traffic\_statuses | Detailed status information for corresponding traffic targets. | + + diff --git a/modules/v2/main.tf b/modules/v2/main.tf new file mode 100644 index 00000000..6a83bcd7 --- /dev/null +++ b/modules/v2/main.tf @@ -0,0 +1,285 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +resource "google_cloud_run_v2_service" "main" { + provider = google-beta + + project = var.project_id + name = var.service_name + location = var.location + description = var.description + labels = var.service_labels + + template { + revision = var.revision + labels = var.template_labels + annotations = var.template_annotations + timeout = var.timeout + service_account = var.service_account + + execution_environment = var.execution_environment + encryption_key = var.encryption_key + max_instance_request_concurrency = var.max_instance_request_concurrency + session_affinity = var.session_affinity + + dynamic "scaling" { + for_each = var.template_scaling[*] + content { + min_instance_count = scaling.value.min_instance_count + max_instance_count = scaling.value.max_instance_count + } + } + + dynamic "vpc_access" { + for_each = var.vpc_access[*] + content { + connector = vpc_access.value.connector + egress = vpc_access.value.egress + dynamic "network_interfaces" { + for_each = vpc_access.value.network_interfaces[*] + content { + network = network_interfaces.value.network + subnetwork = network_interfaces.value.subnetwork + tags = network_interfaces.value.tags + } + } + } + } + + dynamic "containers" { + for_each = var.containers + content { + name = containers.value.container_name + image = containers.value.container_image + command = containers.value.container_command + args = containers.value.container_args + working_dir = containers.value.working_dir + depends_on = containers.value.depends_on_container + + ports { + name = containers.value.ports["name"] + container_port = containers.value.ports["container_port"] + } + + resources { + limits = containers.value.resources.limits + cpu_idle = containers.value.resources.cpu_idle + startup_cpu_boost = containers.value.resources.startup_cpu_boost + } + + dynamic "startup_probe" { + for_each = containers.value.startup_probe[*] + content { + failure_threshold = startup_probe.value.failure_threshold + initial_delay_seconds = startup_probe.value.initial_delay_seconds + timeout_seconds = startup_probe.value.timeout_seconds + period_seconds = startup_probe.value.period_seconds + + dynamic "http_get" { + for_each = startup_probe.value.http_get[*] + content { + path = http_get.value.path + port = http_get.value.port + + dynamic "http_headers" { + for_each = http_get.value.http_headers[*] + content { + name = http_headers.value["name"] + value = http_headers.value["value"] + } + } + } + } + + dynamic "tcp_socket" { + for_each = startup_probe.value.tcp_socket[*] + content { + port = tcp_socket.value.port + } + } + + dynamic "grpc" { + for_each = startup_probe.value.grpc[*] + content { + port = grpc.value.port + service = grpc.value.service + } + } + } + } + + dynamic "liveness_probe" { + for_each = containers.value.liveness_probe[*] + content { + failure_threshold = liveness_probe.value.failure_threshold + initial_delay_seconds = liveness_probe.value.initial_delay_seconds + timeout_seconds = liveness_probe.value.timeout_seconds + period_seconds = liveness_probe.value.period_seconds + + dynamic "http_get" { + for_each = liveness_probe.value.http_get[*] + content { + path = http_get.value.path + port = http_get.value.port + + dynamic "http_headers" { + for_each = http_get.value.http_headers[*] + content { + name = http_headers.value["name"] + value = http_headers.value["value"] + } + } + } + } + + dynamic "tcp_socket" { + for_each = liveness_probe.value.tcp_socket[*] + content { + port = tcp_socket.value.port + } + } + + dynamic "grpc" { + for_each = liveness_probe.value.grpc[*] + content { + port = grpc.value.port + service = grpc.value.service + } + } + } + } + + dynamic "env" { + for_each = containers.value.env_vars + content { + name = env.key + value = env.value + } + } + + dynamic "env" { + for_each = containers.value.env_secret_vars + content { + name = env.key + value_source { + secret_key_ref { + secret = env.value.secret + version = env.value.version + } + } + } + } + + dynamic "volume_mounts" { + for_each = containers.value.volume_mounts + content { + name = volume_mounts.value["name"] + mount_path = volume_mounts.value["mount_path"] + } + } + } + } // containers + + dynamic "volumes" { + for_each = var.volumes + content { + name = volumes.value["name"] + + dynamic "secret" { + for_each = volumes.value.secret[*] + content { + secret = secret.value["secret"] + items { + path = secret.value.items["path"] + version = secret.value.items["version"] + mode = secret.value.items["mode"] + } + } + } + + dynamic "cloud_sql_instance" { + for_each = volumes.value.cloud_sql_instance[*] + content { + instances = cloud_sql_instance.value["instances"] + } + } + dynamic "empty_dir" { + for_each = volumes.value.empty_dir[*] + content { + medium = empty_dir.value["medium"] + size_limit = empty_dir.value["size_limit"] + } + } + dynamic "gcs" { + for_each = volumes.value.gcs[*] + content { + bucket = gcs.value["medium"] + read_only = gcs.value["size_limit"] + } + } + dynamic "nfs" { + for_each = volumes.value.nfs[*] + content { + server = nfs.value["medium"] + path = nfs.value["path"] + read_only = nfs.value["size_limit"] + } + } + } + } + } // template + + annotations = var.service_annotations + client = var.client.name + client_version = var.client.version + ingress = var.ingress + launch_stage = var.launch_stage + custom_audiences = var.custom_audiences + + dynamic "binary_authorization" { + for_each = var.binary_authorization[*] + content { + breakglass_justification = binary_authorization.value.breakglass_justification + use_default = binary_authorization.value.use_default + } + } + + dynamic "scaling" { + for_each = var.service_scaling[*] + content { + min_instance_count = scaling.value.min_instance_count + } + } + + dynamic "traffic" { + for_each = var.traffic + content { + percent = traffic.value.percent + type = traffic.value.type + revision = traffic.value.revision + tag = traffic.value.tag + } + } +} + +resource "google_cloud_run_v2_service_iam_member" "authorize" { + for_each = toset(var.members) + location = google_cloud_run_v2_service.main.location + project = google_cloud_run_v2_service.main.project + name = google_cloud_run_v2_service.main.name + role = "roles/run.invoker" + member = each.value +} diff --git a/modules/v2/metadata.yaml b/modules/v2/metadata.yaml new file mode 100644 index 00000000..fb736de0 --- /dev/null +++ b/modules/v2/metadata.yaml @@ -0,0 +1,325 @@ +# 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. + +apiVersion: blueprints.cloud.google.com/v1alpha1 +kind: BlueprintMetadata +metadata: + name: terraform-google-cloud-run-v-2 + annotations: + config.kubernetes.io/local-config: "true" +spec: + info: + title: Cloud Run v2 Service + source: + repo: https://github.com/GoogleCloudPlatform/terraform-google-cloud-run.git + sourceType: git + dir: /modules/v2 + version: 0.10.0 + actuationTool: + flavor: Terraform + version: ">= 1.3" + description: {} + content: + examples: + - name: cloud_run_vpc_connector + location: examples/cloud_run_vpc_connector + - name: secure_cloud_run + location: examples/secure_cloud_run + - name: secure_cloud_run_standalone + location: examples/secure_cloud_run_standalone + - name: simple_cloud_run + location: examples/simple_cloud_run + - name: simple_cloud_run_with_cmek + location: examples/simple_cloud_run_with_cmek + - name: simple_job_exec + location: examples/simple_job_exec + - name: v2 + location: examples/v2 + interfaces: + variables: + - name: binary_authorization + description: Settings for the Binary Authorization feature. + varType: |- + object({ + breakglass_justification = optional(bool) # If present, indicates to use Breakglass using this justification. If useDefault is False, then it must be empty. For more information on breakglass, see https://cloud.google.com/binary-authorization/docs/using-breakglass + use_default = optional(bool) #If True, indicates to use the default project's binary authorization policy. If False, binary authorization will be disabled. + }) + - name: client + description: Arbitrary identifier for the API client and version identifier + varType: |- + object({ + name = optional(string, null) + version = optional(string, null) + }) + defaultValue: {} + - name: containers + description: Map of container images for the service + varType: |- + list(object({ + container_name = optional(string, null) + container_image = string + working_dir = optional(string, null) + depends_on_container = optional(list(string), null) + container_args = optional(list(string), null) + container_command = optional(list(string), null) + env_vars = optional(map(string), {}) + env_secret_vars = optional(map(object({ + secret = string + version = string + })), {}) + volume_mounts = optional(list(object({ + name = string + mount_path = string + })), []) + ports = optional(object({ + name = optional(string) + container_port = optional(number) + }), { + name = "http1" + container_port = 8080 + }) + resources = optional(object({ + limits = optional(object({ + cpu = optional(string) + memory = optional(string) + })) + cpu_idle = optional(bool, true) + startup_cpu_boost = optional(bool, false) + }), {}) + startup_probe = optional(object({ + failure_threshold = optional(number, null) + initial_delay_seconds = optional(number, null) + timeout_seconds = optional(number, null) + period_seconds = optional(number, null) + http_get = optional(object({ + path = optional(string) + port = optional(string) + http_headers = optional(list(object({ + name = string + value = string + })), null) + }), null) + tcp_socket = optional(object({ + port = optional(number) + }), null) + grpc = optional(object({ + port = optional(number) + service = optional(string) + }), null) + }), null) + liveness_probe = optional(object({ + failure_threshold = optional(number, null) + initial_delay_seconds = optional(number, null) + timeout_seconds = optional(number, null) + period_seconds = optional(number, null) + http_get = optional(object({ + path = optional(string) + port = optional(string) + http_headers = optional(list(object({ + name = string + value = string + })), null) + }), null) + grpc = optional(object({ + port = optional(number) + service = optional(string) + }), null) + }), null) + })) + required: true + - name: custom_audiences + description: One or more custom audiences that you want this service to support. Specify each custom audience as the full URL in a string. Refer https://cloud.google.com/run/docs/configuring/custom-audiences + varType: list(string) + - name: description + description: Cloud Run service description. This field currently has a 512-character limit. + varType: string + - name: encryption_key + description: A reference to a customer managed encryption key (CMEK) to use to encrypt this container image. + varType: string + - name: execution_environment + description: The sandbox environment to host this Revision. + varType: string + defaultValue: EXECUTION_ENVIRONMENT_GEN2 + - name: ingress + description: Provides the ingress settings for this Service. On output, returns the currently observed ingress settings, or INGRESS_TRAFFIC_UNSPECIFIED if no revision is active. + varType: string + defaultValue: INGRESS_TRAFFIC_ALL + - name: launch_stage + description: The launch stage as defined by Google Cloud Platform Launch Stages. Cloud Run supports ALPHA, BETA, and GA. If no value is specified, GA is assumed. + varType: string + defaultValue: GA + - name: location + description: Cloud Run service deployment location + varType: string + required: true + - name: max_instance_request_concurrency + description: Sets the maximum number of requests that each serving instance can receive + varType: string + - name: members + description: Users/SAs to be given invoker access to the service + varType: list(string) + defaultValue: [] + - name: project_id + description: The project ID to deploy to + varType: string + required: true + - name: revision + description: The unique name for the revision. If this field is omitted, it will be automatically generated based on the Service name + varType: string + - name: service_account + description: Email address of the IAM service account associated with the revision of the service + varType: string + - name: service_annotations + description: Unstructured key value map that may be set by external tools to store and arbitrary metadata. They are not queryable and should be preserved when modifying objects. Refer https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#annotations + varType: map(string) + defaultValue: {} + - name: service_labels + description: Unstructured key value map that can be used to organize and categorize objects. For more information, visit https://cloud.google.com/resource-manager/docs/creating-managing-labels or https://cloud.google.com/run/docs/configuring/labels + varType: map(string) + defaultValue: {} + - name: service_name + description: The name of the Cloud Run service to create + varType: string + required: true + - name: service_scaling + description: Scaling settings that apply to the whole service + varType: |- + object({ + min_instance_count = optional(number) + }) + - name: session_affinity + description: Enables session affinity. For more information, go to https://cloud.google.com/run/docs/configuring/session-affinity + varType: string + - name: template_annotations + description: Unstructured key value map that may be set by external tools to store and arbitrary metadata. They are not queryable and should be preserved when modifying objects. Refer https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#annotations + varType: map(string) + defaultValue: {} + - name: template_labels + description: Unstructured key value map that can be used to organize and categorize objects. For more information, visit https://cloud.google.com/resource-manager/docs/creating-managing-labels or https://cloud.google.com/run/docs/configuring/labels + varType: map(string) + defaultValue: {} + - name: template_scaling + description: Scaling settings for this Revision. + varType: |- + object({ + min_instance_count = optional(number) + max_instance_count = optional(number) + }) + defaultValue: {} + - name: timeout + description: Max allowed time for an instance to respond to a request. A duration in seconds with up to nine fractional digits, ending with 's' + varType: string + - name: traffic + description: Specifies how to distribute traffic over a collection of Revisions belonging to the Service. If traffic is empty or not provided, defaults to 100% traffic to the latest Ready Revision. + varType: |- + list(object({ + type = optional(string, "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST") + percent = optional(number, 100) + revision = optional(string, null) + tag = optional(string, null) + })) + defaultValue: [] + - name: volumes + description: Volumes needed for environment variables (when using secret) + varType: |- + list(object({ + name = string + secret = object({ + secret = string + default_mode = optional(string) + items = optional(object({ + path = string + version = optional(string) + mode = optional(string) + })) + }) + cloud_sql_instance = optional(object({ + instances = optional(string) + })) + empty_dir = optional(object({ + medium = optional(string) + size_limit = optional(string) + })) + gcs = optional(object({ + bucket = string + read_only = optional(string) + })) + nfs = optional(object({ + server = string + path = string + read_only = optional(string) + })) + })) + defaultValue: [] + - name: vpc_access + description: VPC Access configuration to use for this Task. For more information, visit https://cloud.google.com/run/docs/configuring/connecting-vpc + varType: |- + object({ + connector = optional(string) + egress = optional(string) + network_interfaces = optional(object({ + network = optional(string) + subnetwork = optional(string) + tags = optional(list(string)) + })) + }) + defaultValue: {} + outputs: + - name: creator + description: Email address of the authenticated creator. + - name: effective_annotations + description: All of annotations (key/value pairs) present on the resource in GCP, including the annotations configured through Terraform, other clients and services. + - name: last_modifier + description: Email address of the last authenticated modifier. + - name: latest_created_revision + description: Name of the last created revision. See comments in reconciling for additional information on reconciliation process in Cloud Run. + - name: latest_ready_revision + description: Name of the latest revision that is serving traffic. See comments in reconciling for additional information on reconciliation process in Cloud Run. + - name: location + description: Location in which the Cloud Run service was created + - name: observed_generation + description: The generation of this Service currently serving traffic. + - name: project_id + description: Google Cloud project in which the service was created + - name: service_id + description: Unique Identifier for the created service with format projects/{{project}}/locations/{{location}}/services/{{name}} + - name: service_name + description: Name of the created service + - name: service_uri + description: The main URI in which this Service is serving traffic. + - name: traffic_statuses + description: Detailed status information for corresponding traffic targets. + requirements: + roles: + - level: Project + roles: + - roles/owner + - level: Project + roles: + - roles/resourcemanager.folderAdmin + - roles/resourcemanager.projectCreator + - roles/resourcemanager.projectDeleter + - level: Project + roles: + - roles/accesscontextmanager.policyAdmin + - roles/orgpolicy.policyAdmin + services: + - cloudresourcemanager.googleapis.com + - storage-api.googleapis.com + - serviceusage.googleapis.com + - run.googleapis.com + - cloudkms.googleapis.com + - iam.googleapis.com + - accesscontextmanager.googleapis.com + - cloudbilling.googleapis.com diff --git a/modules/v2/outputs.tf b/modules/v2/outputs.tf new file mode 100644 index 00000000..369e3b87 --- /dev/null +++ b/modules/v2/outputs.tf @@ -0,0 +1,75 @@ +/** + * 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. + */ + +output "project_id" { + value = google_cloud_run_v2_service.main.project + description = "Google Cloud project in which the service was created" +} + +output "service_uri" { + value = google_cloud_run_v2_service.main.uri + description = "The main URI in which this Service is serving traffic." +} + +output "service_id" { + value = google_cloud_run_v2_service.main.id + description = "Unique Identifier for the created service with format projects/{{project}}/locations/{{location}}/services/{{name}}" +} + +output "service_name" { + value = google_cloud_run_v2_service.main.name + description = "Name of the created service" +} + +output "location" { + value = google_cloud_run_v2_service.main.location + description = "Location in which the Cloud Run service was created" +} + +output "creator" { + value = google_cloud_run_v2_service.main.creator + description = "Email address of the authenticated creator." +} + +output "last_modifier" { + value = google_cloud_run_v2_service.main.last_modifier + description = "Email address of the last authenticated modifier." +} + +output "observed_generation" { + value = google_cloud_run_v2_service.main.observed_generation + description = "The generation of this Service currently serving traffic." +} + +output "latest_ready_revision" { + value = google_cloud_run_v2_service.main.latest_ready_revision + description = "Name of the latest revision that is serving traffic. See comments in reconciling for additional information on reconciliation process in Cloud Run." +} + +output "latest_created_revision" { + value = google_cloud_run_v2_service.main.latest_created_revision + description = "Name of the last created revision. See comments in reconciling for additional information on reconciliation process in Cloud Run." +} + +output "effective_annotations" { + value = google_cloud_run_v2_service.main.effective_annotations + description = "All of annotations (key/value pairs) present on the resource in GCP, including the annotations configured through Terraform, other clients and services." +} + +output "traffic_statuses" { + value = google_cloud_run_v2_service.main.traffic_statuses + description = "Detailed status information for corresponding traffic targets." +} diff --git a/modules/v2/variables.tf b/modules/v2/variables.tf new file mode 100644 index 00000000..5d1306ce --- /dev/null +++ b/modules/v2/variables.tf @@ -0,0 +1,313 @@ +/** + * 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. + */ + +// service +variable "project_id" { + description = "The project ID to deploy to" + type = string +} + +variable "service_name" { + description = "The name of the Cloud Run service to create" + type = string +} + +variable "location" { + description = "Cloud Run service deployment location" + type = string +} + +variable "description" { + description = "Cloud Run service description. This field currently has a 512-character limit." + type = string + default = null +} + +variable "traffic" { + type = list(object({ + type = optional(string, "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST") + percent = optional(number, 100) + revision = optional(string, null) + tag = optional(string, null) + })) + description = "Specifies how to distribute traffic over a collection of Revisions belonging to the Service. If traffic is empty or not provided, defaults to 100% traffic to the latest Ready Revision." + default = [] +} + +variable "service_scaling" { + type = object({ + min_instance_count = optional(number) + }) + description = "Scaling settings that apply to the whole service" + default = null +} + +variable "service_labels" { + type = map(string) + description = "Unstructured key value map that can be used to organize and categorize objects. For more information, visit https://cloud.google.com/resource-manager/docs/creating-managing-labels or https://cloud.google.com/run/docs/configuring/labels" + default = {} +} + +variable "service_annotations" { + type = map(string) + description = "Unstructured key value map that may be set by external tools to store and arbitrary metadata. They are not queryable and should be preserved when modifying objects. Refer https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#annotations" + default = {} +} + +variable "client" { + type = object({ + name = optional(string, null) + version = optional(string, null) + }) + description = "Arbitrary identifier for the API client and version identifier" + default = {} +} + +variable "ingress" { + type = string + description = "Provides the ingress settings for this Service. On output, returns the currently observed ingress settings, or INGRESS_TRAFFIC_UNSPECIFIED if no revision is active." + default = "INGRESS_TRAFFIC_ALL" + + validation { + condition = contains(["INGRESS_TRAFFIC_ALL", "INGRESS_TRAFFIC_INTERNAL_ONLY", "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"], var.ingress) + error_message = "Allowed values for ingress are \"INGRESS_TRAFFIC_ALL\", \"INGRESS_TRAFFIC_INTERNAL_ONLY\", or \"INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER\"." + } +} + +variable "launch_stage" { + type = string + description = "The launch stage as defined by Google Cloud Platform Launch Stages. Cloud Run supports ALPHA, BETA, and GA. If no value is specified, GA is assumed." + default = "GA" + + validation { + condition = contains(["UNIMPLEMENTED", "PRELAUNCH", "EARLY_ACCESS", "ALPHA", "BETA", "GA", "DEPRECATED"], var.launch_stage) + error_message = "Allowed values for launch_stage are \"UNIMPLEMENTED\", \"PRELAUNCH\", or \"EARLY_ACCESS\", or \"DEPRECATED\", or \"ALPHA\", or \"BETA\", or \"GA\"." + } +} + +variable "custom_audiences" { + type = list(string) + description = "One or more custom audiences that you want this service to support. Specify each custom audience as the full URL in a string. Refer https://cloud.google.com/run/docs/configuring/custom-audiences" + default = null +} + +variable "binary_authorization" { + type = object({ + breakglass_justification = optional(bool) # If present, indicates to use Breakglass using this justification. If useDefault is False, then it must be empty. For more information on breakglass, see https://cloud.google.com/binary-authorization/docs/using-breakglass + use_default = optional(bool) #If True, indicates to use the default project's binary authorization policy. If False, binary authorization will be disabled. + }) + description = "Settings for the Binary Authorization feature." + default = null +} + +// Template +variable "revision" { + description = "The unique name for the revision. If this field is omitted, it will be automatically generated based on the Service name" + type = string + default = null +} + +variable "template_scaling" { + type = object({ + min_instance_count = optional(number) + max_instance_count = optional(number) + }) + description = "Scaling settings for this Revision." + default = {} +} + +variable "vpc_access" { + type = object({ + connector = optional(string) + egress = optional(string) + network_interfaces = optional(object({ + network = optional(string) + subnetwork = optional(string) + tags = optional(list(string)) + })) + }) + description = "VPC Access configuration to use for this Task. For more information, visit https://cloud.google.com/run/docs/configuring/connecting-vpc" + default = {} +} + +variable "template_labels" { + type = map(string) + description = "Unstructured key value map that can be used to organize and categorize objects. For more information, visit https://cloud.google.com/resource-manager/docs/creating-managing-labels or https://cloud.google.com/run/docs/configuring/labels" + default = {} +} + +variable "template_annotations" { + type = map(string) + description = "Unstructured key value map that may be set by external tools to store and arbitrary metadata. They are not queryable and should be preserved when modifying objects. Refer https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#annotations" + default = {} +} + +variable "timeout" { + type = string + description = "Max allowed time for an instance to respond to a request. A duration in seconds with up to nine fractional digits, ending with 's'" + default = null +} + +variable "service_account" { + type = string + description = "Email address of the IAM service account associated with the revision of the service" + default = null +} + +variable "encryption_key" { + description = "A reference to a customer managed encryption key (CMEK) to use to encrypt this container image." + type = string + default = null +} + +variable "max_instance_request_concurrency" { + type = string + description = "Sets the maximum number of requests that each serving instance can receive" + default = null +} + +variable "session_affinity" { + type = string + description = "Enables session affinity. For more information, go to https://cloud.google.com/run/docs/configuring/session-affinity" + default = null +} + +variable "execution_environment" { + type = string + description = "The sandbox environment to host this Revision." + default = "EXECUTION_ENVIRONMENT_GEN2" + + validation { + condition = contains(["EXECUTION_ENVIRONMENT_GEN1", "EXECUTION_ENVIRONMENT_GEN2"], var.execution_environment) + error_message = "Allowed values for ingress are \"EXECUTION_ENVIRONMENT_GEN1\", \"EXECUTION_ENVIRONMENT_GEN2\"." + } +} + +variable "volumes" { + type = list(object({ + name = string + secret = object({ + secret = string + default_mode = optional(string) + items = optional(object({ + path = string + version = optional(string) + mode = optional(string) + })) + }) + cloud_sql_instance = optional(object({ + instances = optional(string) + })) + empty_dir = optional(object({ + medium = optional(string) + size_limit = optional(string) + })) + gcs = optional(object({ + bucket = string + read_only = optional(string) + })) + nfs = optional(object({ + server = string + path = string + read_only = optional(string) + })) + })) + description = "Volumes needed for environment variables (when using secret)" + default = [] +} + +// Containers +variable "containers" { + type = list(object({ + container_name = optional(string, null) + container_image = string + working_dir = optional(string, null) + depends_on_container = optional(list(string), null) + container_args = optional(list(string), null) + container_command = optional(list(string), null) + env_vars = optional(map(string), {}) + env_secret_vars = optional(map(object({ + secret = string + version = string + })), {}) + volume_mounts = optional(list(object({ + name = string + mount_path = string + })), []) + ports = optional(object({ + name = optional(string) + container_port = optional(number) + }), { + name = "http1" + container_port = 8080 + }) + resources = optional(object({ + limits = optional(object({ + cpu = optional(string) + memory = optional(string) + })) + cpu_idle = optional(bool, true) + startup_cpu_boost = optional(bool, false) + }), {}) + startup_probe = optional(object({ + failure_threshold = optional(number, null) + initial_delay_seconds = optional(number, null) + timeout_seconds = optional(number, null) + period_seconds = optional(number, null) + http_get = optional(object({ + path = optional(string) + port = optional(string) + http_headers = optional(list(object({ + name = string + value = string + })), null) + }), null) + tcp_socket = optional(object({ + port = optional(number) + }), null) + grpc = optional(object({ + port = optional(number) + service = optional(string) + }), null) + }), null) + liveness_probe = optional(object({ + failure_threshold = optional(number, null) + initial_delay_seconds = optional(number, null) + timeout_seconds = optional(number, null) + period_seconds = optional(number, null) + http_get = optional(object({ + path = optional(string) + port = optional(string) + http_headers = optional(list(object({ + name = string + value = string + })), null) + }), null) + grpc = optional(object({ + port = optional(number) + service = optional(string) + }), null) + }), null) + })) + description = "Map of container images for the service" +} + +// IAM +variable "members" { + type = list(string) + description = "Users/SAs to be given invoker access to the service" + default = [] +} diff --git a/modules/v2/versions.tf b/modules/v2/versions.tf new file mode 100644 index 00000000..ac68e57e --- /dev/null +++ b/modules/v2/versions.tf @@ -0,0 +1,38 @@ +/** + * 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. + */ + +terraform { + required_version = ">= 1.3" + + required_providers { + google = { + source = "hashicorp/google" + version = "< 6" + } + google-beta = { + source = "hashicorp/google-beta" + version = "< 6" + } + } + + provider_meta "google" { + module_name = "blueprints/terraform/terraform-google-cloud-run:v2/v0.10.0" + } + + provider_meta "google-beta" { + module_name = "blueprints/terraform/terraform-google-cloud-run:v2/v0.10.0" + } +} diff --git a/test/integration/v2/v2_test.go b/test/integration/v2/v2_test.go new file mode 100644 index 00000000..08dff118 --- /dev/null +++ b/test/integration/v2/v2_test.go @@ -0,0 +1,40 @@ +// 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. + +package v2 + +import ( + "fmt" + "testing" + + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/gcloud" + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft" + "github.com/stretchr/testify/assert" +) + +func Testv2(t *testing.T) { + run_v2 := tft.NewTFBlueprintTest(t) + + run_v2.DefineVerify(func(assert *assert.Assertions) { + projectID := run_v2.GetTFSetupStringOutput("project_id") + serviceName := run_v2.GetTFSetupStringOutput("service_name") + serviceLocation := run_v2.GetTFSetupStringOutput("service_location") + + run_cmd := gcloud.Run(t, "run services describe", gcloud.WithCommonArgs([]string{serviceName, "--project", projectID, "--region", serviceLocation, "--format", "json"})) + + // T01: Verify if the Cloud Run Service deployed is in ACTIVE state + assert.Equal("ACTIVE", run_cmd.Get("state").String(), fmt.Sprintf("Should be ACTIVE. Cloud Run service is not successfully deployed.")) + }) + run_v2.Test() +}