diff --git a/README.md b/README.md index 9f988664a8c678c7bfc6d81e0710031f8d27aec1..363b6b543a62f39800283870a6ad446a43353793 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ For more information on how the pre-deploy Cloud Run job works see the | <a name="input_template_labels"></a> [template\_labels](#input\_template\_labels) | A set of key/value label pairs to assign to the Cloud Run service revision. | `map(string)` | `{}` | no | | <a name="input_timeout_seconds"></a> [timeout\_seconds](#input\_timeout\_seconds) | The maximum duration, in seconds, the instance is allowed for responding to a<br>request. Maximum is 900s. | `string` | `"300s"` | no | | <a name="input_traffic"></a> [traffic](#input\_traffic) | Configure traffic allocation between one or more service revisions. | <pre>list(object({<br> type = optional(string)<br> revision = optional(string)<br> percent = optional(number)<br> tag = optional(string)<br> }))</pre> | `[]` | no | -| <a name="input_volumes"></a> [volumes](#input\_volumes) | Configure one or more volumes for the service. See<br>https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#nested_volumes<br>for more information on these options. | <pre>list(object({<br> name = string<br> secret = optional(object({<br> secret = string<br> default_mode = optional(number)<br> items = optional(list(object({<br> path = string<br> version = optional(string)<br> mode = optional(number)<br> })), [])<br> }))<br> cloud_sql_instance = optional(object({<br> instances = optional(list(string))<br> }))<br> }))</pre> | `[]` | no | +| <a name="input_volumes"></a> [volumes](#input\_volumes) | Configure one or more volumes for the service. See<br>https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#nested_volumes<br>for more information on these options. | <pre>list(object({<br> name = string<br> secret = optional(object({<br> secret = string<br> default_mode = optional(number)<br> items = optional(list(object({<br> path = string<br> version = optional(string)<br> mode = optional(number)<br> })), [])<br> }))<br> gcs = optional(object({<br> bucket = string<br> read_only = optional(bool)<br> }))<br> cloud_sql_instance = optional(object({<br> instances = optional(list(string))<br> }))<br> }))</pre> | `[]` | no | | <a name="input_vpc_access"></a> [vpc\_access](#input\_vpc\_access) | Configure VPC access for the Cloud Run service. For more information on these<br>options see<br>https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#nested_vpc_access | <pre>object({<br> connector = optional(string)<br> egress = optional(string)<br> network_interfaces = optional(object({<br> network = optional(string)<br> subnetwork = optional(string)<br> tags = optional(string)<br> }))<br> })</pre> | `null` | no | | <a name="input_vpc_access_connector_max_throughput"></a> [vpc\_access\_connector\_max\_throughput](#input\_vpc\_access\_connector\_max\_throughput) | Optional. The maximum throughput of the connector in megabytes per second.<br>Defaults to 300. | `number` | `300` | no | | <a name="input_vpc_access_connector_min_throughput"></a> [vpc\_access\_connector\_min\_throughput](#input\_vpc\_access\_connector\_min\_throughput) | Optional. The minimum throughput of the connector in megabytes per second.<br>Defaults to 200. | `number` | `200` | no | diff --git a/main.tf b/main.tf index 49a2b61d25ad8c0601294c755929c245ed216ab3..e6f08248eb59c34d08952613f7e17b2ddbbb10be 100644 --- a/main.tf +++ b/main.tf @@ -223,6 +223,13 @@ resource "google_cloud_run_v2_service" "webapp" { instances = cloud_sql_instance.value["instances"] } } + dynamic "gcs" { + for_each = volume.value["gcs"] != null ? [volume.value["gcs"]] : [] + content { + bucket = gcs.value["bucket"] + read_only = gcs.value["read_only"] != null ? gcs.value["read_only"] : true + } + } } } dynamic "volumes" { diff --git a/tests/cleanup.sh b/tests/cleanup.sh index a2a09a447e9b844688e2fd04fbc25c668b04a14e..fa1f77dc23dd3597364b5ba6ea220f3aa900e2e5 100755 --- a/tests/cleanup.sh +++ b/tests/cleanup.sh @@ -67,6 +67,15 @@ for bucket in "${buckets[@]}"; do gcloud --project="$GCP_PROJECT" storage rm -r "$bucket" --quiet done +mapfile -t bucketsgcs < <( + gcloud --project="$GCP_PROJECT" storage buckets list \ + --filter="name ~ ${TEST_PREFIX::8}-gcs" --format="value(storage_url)" +) + +for bucket in "${bucketsgcs[@]}"; do + gcloud --project="$GCP_PROJECT" storage rm -r "$bucket" --quiet +done + echo "Cleaning up Cloud Monitoring resources..." mapfile -t alert_policies < <( gcloud alpha --project="$GCP_PROJECT" monitoring policies list \ diff --git a/tests/cloud_run_service.tftest.hcl b/tests/cloud_run_service.tftest.hcl index b3c0fa6328415a3c9d6cb5c226298a0116ff4b70..d65ab839042e5ca60642bff459baae78c504324b 100644 --- a/tests/cloud_run_service.tftest.hcl +++ b/tests/cloud_run_service.tftest.hcl @@ -231,6 +231,57 @@ run "test_env_vars_and_secrets" { } } +run "setup_gcs_bucket" { + variables { + create_gcs_bucket = true + } + + module { + source = "./tests/setup" + } +} + +run "test_gcs_volume" { + variables { + name = run.setup_gcs_bucket.random_name + execution_environment = "EXECUTION_ENVIRONMENT_GEN2" + containers = { + webapp = { + image = "us-docker.pkg.dev/cloudrun/container/hello" + volume_mounts = [ + { + name = "test-gcs-volume", + mount_path = "/gcs" + } + ] + } + } + volumes = [ + { + name = "test-gcs-volume" + gcs = { + bucket = run.setup_gcs_bucket.gcs_bucket_name + read_only = true + } + } + ] + deletion_protection = false + } + + assert { + condition = google_cloud_run_v2_service.webapp.template[0].containers[0].volume_mounts[0].name == "test-gcs-volume" + error_message = "A volume mount with the name 'test-gcs-volume' should be created." + } + assert { + condition = google_cloud_run_v2_service.webapp.template[0].containers[0].volume_mounts[0].mount_path == "/gcs" + error_message = "A volume mount with the mount path '/gcs' should be created." + } + assert { + condition = google_cloud_run_v2_service.webapp.template[0].volumes[0].name == "test-gcs-volume" + error_message = "A volume with the name 'test-gcs-volume' should be created." + } +} + run "test_service_with_multiple_containers" { variables { name = run.setup.random_name diff --git a/tests/load_balancer.tftest.hcl b/tests/load_balancer.tftest.hcl index 097786cd776fede24e9bf4360f6f5ee5b31bf778..d17f9819985670c218a2258f5ae90739fd467c8e 100644 --- a/tests/load_balancer.tftest.hcl +++ b/tests/load_balancer.tftest.hcl @@ -176,22 +176,22 @@ run "test_service_with_load_balancer_enabled_and_load_balancer_alerting_defaults } assert { - condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0] != null + condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0] != null error_message = "An alert policy resource should be created." } assert { - condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0].enabled == true + condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0].enabled == true error_message = "Load balancer alert policy should be enabled." } assert { - condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0].conditions[0].condition_threshold[0].threshold_value == 10 + condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0].conditions[0].condition_threshold[0].threshold_value == 10 error_message = "Load balancer alert condition triggers after 10 5XX responses." } assert { - condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0].conditions[0].condition_threshold[0].aggregations[0].alignment_period == "60s" + condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0].conditions[0].condition_threshold[0].aggregations[0].alignment_period == "60s" error_message = "Load balancer alert condition aggregates over 60s." } } @@ -210,24 +210,24 @@ run "test_service_with_load_balancer_enabled_and_load_balancer_alerting" { } deletion_protection = false alerting_load_balancer_server_errors = { - enabled = true + enabled = true alignment_period = "120s" - threshold = 100 + threshold = 100 } } assert { - condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0] != null + condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0] != null error_message = "An alert policy resource should be created." } assert { - condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0].conditions[0].condition_threshold[0].threshold_value == 100 + condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0].conditions[0].condition_threshold[0].threshold_value == 100 error_message = "Load balancer alert condition triggers after 100 5XX responses." } assert { - condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0].conditions[0].condition_threshold[0].aggregations[0].alignment_period == "120s" + condition = google_monitoring_alert_policy.load_balancer_server_error_alerts[0].conditions[0].condition_threshold[0].aggregations[0].alignment_period == "120s" error_message = "Load balancer alert condition aggregates over 120s." } } diff --git a/tests/setup/main.tf b/tests/setup/main.tf index 357276a6d870b6055a68e5096960777198d68fd6..e4f6040fba1b0774d139b158784d7f259ae68508 100644 --- a/tests/setup/main.tf +++ b/tests/setup/main.tf @@ -77,3 +77,18 @@ resource "google_vpc_access_connector" "test" { name = google_compute_subnetwork.test[0].name } } + +resource "google_storage_bucket" "test_bucket" { + count = var.create_gcs_bucket ? 1 : 0 + name = "${random_id.name.hex}-gcs" + project = var.project + location = "EU" +} + +resource "google_storage_bucket_iam_member" "test" { + count = var.create_gcs_bucket ? 1 : 0 + bucket = google_storage_bucket.test_bucket[0].name + role = "roles/storage.admin" + member = "serviceAccount:${random_id.name.hex}-run@${var.project}.iam.gserviceaccount.com" + depends_on = [google_storage_bucket.test_bucket] +} diff --git a/tests/setup/outputs.tf b/tests/setup/outputs.tf index de4e19dfffbcdcf971a6db66c80b6b4d84c09689..4b0f9c0e226925329243bad0c43334135675217a 100644 --- a/tests/setup/outputs.tf +++ b/tests/setup/outputs.tf @@ -22,3 +22,8 @@ output "vpc_connector_id" { description = "The ID of the VPC Access Connector object created to test VPC access for the Cloud Run service." value = var.create_vpc_connector ? google_vpc_access_connector.test[0].id : null } + +output "gcs_bucket_name" { + description = "Storage bucket name for testing the GCS volume mount" + value = var.create_gcs_bucket ? google_storage_bucket.test_bucket[0].name : null +} diff --git a/tests/setup/variables.tf b/tests/setup/variables.tf index 1cc1db59d328d1d249d870ed0f5c4a3c515a8e0b..abf9153238176e6af3b6eadb348626351b2a8987 100644 --- a/tests/setup/variables.tf +++ b/tests/setup/variables.tf @@ -8,6 +8,12 @@ variable "region" { type = string } +variable "create_gcs_bucket" { + description = "If true, gcs bucket will be deployed." + type = bool + default = false +} + variable "create_test_sql_instances" { description = "If true, two SQL instances will be deployed for testing." type = bool diff --git a/variables.tf b/variables.tf index e68b3412b7e1e965bd42e523e7d27b801377f2ff..4fd2c1c2f75070be3f97da982bd8466cd3ee6df1 100644 --- a/variables.tf +++ b/variables.tf @@ -264,6 +264,10 @@ EOI mode = optional(number) })), []) })) + gcs = optional(object({ + bucket = string + read_only = optional(bool) + })) cloud_sql_instance = optional(object({ instances = optional(list(string)) }))