diff --git a/.changelog/9746.txt b/.changelog/9746.txt new file mode 100644 index 0000000000..8fbe8a23eb --- /dev/null +++ b/.changelog/9746.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +cloudrunv2: support mounting Cloud Storage buckets using GCSFuse in Jobs (beta). +``` \ No newline at end of file diff --git a/google-beta/services/cloudrunv2/resource_cloud_run_v2_job.go b/google-beta/services/cloudrunv2/resource_cloud_run_v2_job.go index 9119abf01c..cd102c62eb 100644 --- a/google-beta/services/cloudrunv2/resource_cloud_run_v2_job.go +++ b/google-beta/services/cloudrunv2/resource_cloud_run_v2_job.go @@ -313,6 +313,26 @@ A duration in seconds with up to nine fractional digits, ending with 's'. Exampl }, }, }, + "gcs": { + Type: schema.TypeList, + Optional: true, + Description: `Cloud Storage bucket mounted as a volume using GCSFuse. This feature requires the launch stage to be set to ALPHA or BETA.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bucket": { + Type: schema.TypeString, + Required: true, + Description: `Name of the cloud storage bucket to back the volume. The resource service account must have permission to access the bucket.`, + }, + "read_only": { + Type: schema.TypeBool, + Optional: true, + Description: `If true, mount this volume as read-only in all mounts. If false, mount this volume as read-write.`, + }, + }, + }, + }, "secret": { Type: schema.TypeList, Optional: true, @@ -1551,6 +1571,7 @@ func flattenCloudRunV2JobTemplateTemplateVolumes(v interface{}, d *schema.Resour "secret": flattenCloudRunV2JobTemplateTemplateVolumesSecret(original["secret"], d, config), "cloud_sql_instance": flattenCloudRunV2JobTemplateTemplateVolumesCloudSqlInstance(original["cloudSqlInstance"], d, config), "empty_dir": flattenCloudRunV2JobTemplateTemplateVolumesEmptyDir(original["emptyDir"], d, config), + "gcs": flattenCloudRunV2JobTemplateTemplateVolumesGcs(original["gcs"], d, config), }) } return transformed @@ -1682,6 +1703,29 @@ func flattenCloudRunV2JobTemplateTemplateVolumesEmptyDirSizeLimit(v interface{}, return v } +func flattenCloudRunV2JobTemplateTemplateVolumesGcs(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["bucket"] = + flattenCloudRunV2JobTemplateTemplateVolumesGcsBucket(original["bucket"], d, config) + transformed["read_only"] = + flattenCloudRunV2JobTemplateTemplateVolumesGcsReadOnly(original["readOnly"], d, config) + return []interface{}{transformed} +} +func flattenCloudRunV2JobTemplateTemplateVolumesGcsBucket(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenCloudRunV2JobTemplateTemplateVolumesGcsReadOnly(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + func flattenCloudRunV2JobTemplateTemplateTimeout(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { return v } @@ -2497,6 +2541,13 @@ func expandCloudRunV2JobTemplateTemplateVolumes(v interface{}, d tpgresource.Ter transformed["emptyDir"] = transformedEmptyDir } + transformedGcs, err := expandCloudRunV2JobTemplateTemplateVolumesGcs(original["gcs"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedGcs); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["gcs"] = transformedGcs + } + req = append(req, transformed) } return req, nil @@ -2652,6 +2703,40 @@ func expandCloudRunV2JobTemplateTemplateVolumesEmptyDirSizeLimit(v interface{}, return v, nil } +func expandCloudRunV2JobTemplateTemplateVolumesGcs(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedBucket, err := expandCloudRunV2JobTemplateTemplateVolumesGcsBucket(original["bucket"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedBucket); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["bucket"] = transformedBucket + } + + transformedReadOnly, err := expandCloudRunV2JobTemplateTemplateVolumesGcsReadOnly(original["read_only"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedReadOnly); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["readOnly"] = transformedReadOnly + } + + return transformed, nil +} + +func expandCloudRunV2JobTemplateTemplateVolumesGcsBucket(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunV2JobTemplateTemplateVolumesGcsReadOnly(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + func expandCloudRunV2JobTemplateTemplateTimeout(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { return v, nil } diff --git a/google-beta/services/cloudrunv2/resource_cloud_run_v2_job_test.go b/google-beta/services/cloudrunv2/resource_cloud_run_v2_job_test.go index 5bda46be0d..b9cf7a0f89 100644 --- a/google-beta/services/cloudrunv2/resource_cloud_run_v2_job_test.go +++ b/google-beta/services/cloudrunv2/resource_cloud_run_v2_job_test.go @@ -300,3 +300,89 @@ func testAccCloudRunV2Job_cloudrunv2JobWithDirectVPCUpdate(context map[string]in } `, context) } + +func TestAccCloudRunV2Job_cloudrunv2JobWithGcsUpdate(t *testing.T) { + t.Parallel() + + jobName := fmt.Sprintf("tf-test-cloudrun-service%s", acctest.RandString(t, 10)) + context := map[string]interface{}{ + "job_name": jobName, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckCloudRunV2JobDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccCloudRunV2Job_cloudrunv2JobWithNoVolume(context), + }, + { + ResourceName: "google_cloud_run_v2_job.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "launch_stage"}, + }, + { + Config: testAccCloudRunV2Job_cloudrunv2JobWithGcsVolume(context), + }, + { + ResourceName: "google_cloud_run_v2_job.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"location", "launch_stage"}, + }, + }, + }) +} + +func testAccCloudRunV2Job_cloudrunv2JobWithNoVolume(context map[string]interface{}) string { + return acctest.Nprintf(` + resource "google_cloud_run_v2_job" "default" { + name = "%{job_name}" + location = "us-central1" + launch_stage = "BETA" + template { + template { + containers { + image = "us-docker.pkg.dev/cloudrun/container/job" + } + } + } + + lifecycle { + ignore_changes = [ + launch_stage, + ] + } + } +`, context) +} + +func testAccCloudRunV2Job_cloudrunv2JobWithGcsVolume(context map[string]interface{}) string { + return acctest.Nprintf(` + resource "google_cloud_run_v2_job" "default" { + name = "%{job_name}" + location = "us-central1" + launch_stage = "BETA" + template { + template { + containers { + image = "us-docker.pkg.dev/cloudrun/container/job" + volume_mounts { + name = "gcs" + mount_path = "/mnt/gcs" + } + } + volumes { + name = "gcs" + gcs { + bucket = "gcp-public-data-landsat" + read_only = true + } + } + } + } + } +`, context) +} diff --git a/google-beta/services/cloudrunv2/resource_cloud_run_v2_service.go b/google-beta/services/cloudrunv2/resource_cloud_run_v2_service.go index 2c7b73f3d4..0e5c7622c9 100644 --- a/google-beta/services/cloudrunv2/resource_cloud_run_v2_service.go +++ b/google-beta/services/cloudrunv2/resource_cloud_run_v2_service.go @@ -619,7 +619,7 @@ A duration in seconds with up to nine fractional digits, ending with 's'. Exampl "gcs": { Type: schema.TypeList, Optional: true, - Description: `Represents a GCS Bucket mounted as a volume.`, + Description: `Cloud Storage bucket mounted as a volume using GCSFuse. This feature is only supported in the gen2 execution environment and requires launch-stage to be set to ALPHA or BETA.`, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ diff --git a/google-beta/services/cloudrunv2/resource_cloud_run_v2_service_test.go b/google-beta/services/cloudrunv2/resource_cloud_run_v2_service_test.go index f70980f01a..2efa477bfc 100644 --- a/google-beta/services/cloudrunv2/resource_cloud_run_v2_service_test.go +++ b/google-beta/services/cloudrunv2/resource_cloud_run_v2_service_test.go @@ -110,7 +110,6 @@ resource "google_service_account" "service_account" { } `, context) } - func testAccCloudRunV2Service_cloudrunv2ServiceFullUpdate(context map[string]interface{}) string { return acctest.Nprintf(` resource "google_cloud_run_v2_service" "default" { @@ -209,7 +208,106 @@ resource "google_compute_network" "custom_test" { } `, context) } +func TestAccCloudRunV2Service_cloudrunv2ServiceGcsVolume(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckCloudRunV2ServiceDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccCloudRunV2Service_cloudrunv2ServiceGcsVolume(context), + }, + { + ResourceName: "google_cloud_run_v2_service.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "location", "annotations", "labels", "terraform_labels", "launch_stage"}, + }, + }, + }) +} +func testAccCloudRunV2Service_cloudrunv2ServiceGcsVolume(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_cloud_run_v2_service" "default" { + name = "tf-test-cloudrun-service%{random_suffix}" + description = "description creating" + location = "us-central1" + launch_stage = "BETA" + annotations = { + generated-by = "magic-modules" + } + ingress = "INGRESS_TRAFFIC_ALL" + labels = { + label-1 = "value-1" + } + client = "client-1" + client_version = "client-version-1" + template { + labels = { + label-1 = "value-1" + } + timeout = "300s" + service_account = google_service_account.service_account.email + execution_environment = "EXECUTION_ENVIRONMENT_GEN2" + scaling { + max_instance_count = 3 + min_instance_count = 1 + } + annotations = { + generated-by = "magic-modules" + } + volumes { + name = "gcs" + gcs { + bucket = "gcp-public-data-landsat" + read_only = true + } + } + containers { + name = "container-1" + image = "us-docker.pkg.dev/cloudrun/container/hello" + env { + name = "SOURCE" + value = "remote" + } + env { + name = "TARGET" + value = "home" + } + ports { + name = "h2c" + container_port = 8080 + } + volume_mounts { + name = "gcs" + mount_path = "/mnt/landsat" + } + resources { + cpu_idle = true + startup_cpu_boost = true + limits = { + cpu = "4" + memory = "2Gi" + } + } + } + session_affinity = false + } +} + +resource "google_service_account" "service_account" { + account_id = "tf-test-my-account%{random_suffix}" + display_name = "Test Service Account" +} +`, context) +} func TestAccCloudRunV2Service_cloudrunv2ServiceTCPProbesUpdate(t *testing.T) { t.Parallel() diff --git a/website/docs/r/cloud_run_v2_job.html.markdown b/website/docs/r/cloud_run_v2_job.html.markdown index 90d74c2cba..5e5ff42b77 100644 --- a/website/docs/r/cloud_run_v2_job.html.markdown +++ b/website/docs/r/cloud_run_v2_job.html.markdown @@ -556,6 +556,11 @@ The following arguments are supported: Ephemeral storage used as a shared volume. Structure is [documented below](#nested_empty_dir). +* `gcs` - + (Optional, [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html)) + Cloud Storage bucket mounted as a volume using GCSFuse. This feature requires the launch stage to be set to ALPHA or BETA. + Structure is [documented below](#nested_gcs). + The `secret` block supports: @@ -605,6 +610,16 @@ The following arguments are supported: (Optional) Limit on the storage usable by this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. This field's values are of the 'Quantity' k8s type: https://kubernetes.io/docs/reference/kubernetes-api/common-definitions/quantity/. The default is nil which means that the limit is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes/#emptydir. +The `gcs` block supports: + +* `bucket` - + (Required) + Name of the cloud storage bucket to back the volume. The resource service account must have permission to access the bucket. + +* `read_only` - + (Optional) + If true, mount this volume as read-only in all mounts. If false, mount this volume as read-write. + The `vpc_access` block supports: * `connector` - diff --git a/website/docs/r/cloud_run_v2_service.html.markdown b/website/docs/r/cloud_run_v2_service.html.markdown index e308ab7a26..78df38e653 100644 --- a/website/docs/r/cloud_run_v2_service.html.markdown +++ b/website/docs/r/cloud_run_v2_service.html.markdown @@ -927,7 +927,7 @@ The following arguments are supported: * `gcs` - (Optional) - Represents a GCS Bucket mounted as a volume. + Cloud Storage bucket mounted as a volume using GCSFuse. This feature is only supported in the gen2 execution environment and requires launch-stage to be set to ALPHA or BETA. Structure is [documented below](#nested_gcs). * `nfs` -