diff --git a/.changelog/9752.txt b/.changelog/9752.txt new file mode 100644 index 0000000000..ad24290590 --- /dev/null +++ b/.changelog/9752.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +cloudrun: added `template.spec.volumes.csi` field to `google_cloud_run_service` to support mounting Cloud Storage buckets using GCSFuse (beta) +``` diff --git a/google-beta/services/cloudrun/resource_cloud_run_service.go b/google-beta/services/cloudrun/resource_cloud_run_service.go index 4ba97e3d36..74fd73a363 100644 --- a/google-beta/services/cloudrun/resource_cloud_run_service.go +++ b/google-beta/services/cloudrun/resource_cloud_run_service.go @@ -645,6 +645,38 @@ will use the project's default service account.`, Required: true, Description: `Volume's name.`, }, + "csi": { + Type: schema.TypeList, + Optional: true, + Description: `A filesystem specified by the Container Storage Interface (CSI).`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "driver": { + Type: schema.TypeString, + Required: true, + Description: `Unique name representing the type of file system to be created. Cloud Run supports the following values: + * gcsfuse.run.googleapis.com: Mount a Google Cloud Storage bucket using GCSFuse. This driver requires the + run.googleapis.com/execution-environment annotation to be set to "gen2" and + run.googleapis.com/launch-stage set to "BETA" or "ALPHA".`, + }, + "read_only": { + Type: schema.TypeBool, + Computed: true, + Optional: true, + Description: `If true, all mounts created from this volume will be read-only.`, + }, + "volume_attributes": { + Type: schema.TypeMap, + Optional: true, + Description: `Driver-specific attributes. The following options are supported for available drivers: + * gcsfuse.run.googleapis.com + * bucketName: The name of the Cloud Storage Bucket that backs this volume. The Cloud Run Service identity must have access to this bucket.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, "empty_dir": { Type: schema.TypeList, Optional: true, @@ -2483,6 +2515,7 @@ func flattenCloudRunServiceSpecTemplateSpecVolumes(v interface{}, d *schema.Reso "name": flattenCloudRunServiceSpecTemplateSpecVolumesName(original["name"], d, config), "secret": flattenCloudRunServiceSpecTemplateSpecVolumesSecret(original["secret"], d, config), "empty_dir": flattenCloudRunServiceSpecTemplateSpecVolumesEmptyDir(original["emptyDir"], d, config), + "csi": flattenCloudRunServiceSpecTemplateSpecVolumesCsi(original["csi"], d, config), }) } return transformed @@ -2597,6 +2630,35 @@ func flattenCloudRunServiceSpecTemplateSpecVolumesEmptyDirSizeLimit(v interface{ return v } +func flattenCloudRunServiceSpecTemplateSpecVolumesCsi(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["driver"] = + flattenCloudRunServiceSpecTemplateSpecVolumesCsiDriver(original["driver"], d, config) + transformed["read_only"] = + flattenCloudRunServiceSpecTemplateSpecVolumesCsiReadOnly(original["readOnly"], d, config) + transformed["volume_attributes"] = + flattenCloudRunServiceSpecTemplateSpecVolumesCsiVolumeAttributes(original["volumeAttributes"], d, config) + return []interface{}{transformed} +} +func flattenCloudRunServiceSpecTemplateSpecVolumesCsiDriver(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecVolumesCsiReadOnly(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenCloudRunServiceSpecTemplateSpecVolumesCsiVolumeAttributes(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + func flattenCloudRunServiceSpecTemplateSpecServingState(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { return v } @@ -4113,6 +4175,13 @@ func expandCloudRunServiceSpecTemplateSpecVolumes(v interface{}, d tpgresource.T transformed["emptyDir"] = transformedEmptyDir } + transformedCsi, err := expandCloudRunServiceSpecTemplateSpecVolumesCsi(original["csi"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCsi); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["csi"] = transformedCsi + } + req = append(req, transformed) } return req, nil @@ -4245,6 +4314,58 @@ func expandCloudRunServiceSpecTemplateSpecVolumesEmptyDirSizeLimit(v interface{} return v, nil } +func expandCloudRunServiceSpecTemplateSpecVolumesCsi(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{}) + + transformedDriver, err := expandCloudRunServiceSpecTemplateSpecVolumesCsiDriver(original["driver"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDriver); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["driver"] = transformedDriver + } + + transformedReadOnly, err := expandCloudRunServiceSpecTemplateSpecVolumesCsiReadOnly(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 + } + + transformedVolumeAttributes, err := expandCloudRunServiceSpecTemplateSpecVolumesCsiVolumeAttributes(original["volume_attributes"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedVolumeAttributes); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["volumeAttributes"] = transformedVolumeAttributes + } + + return transformed, nil +} + +func expandCloudRunServiceSpecTemplateSpecVolumesCsiDriver(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecVolumesCsiReadOnly(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandCloudRunServiceSpecTemplateSpecVolumesCsiVolumeAttributes(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + func expandCloudRunServiceSpecTemplateSpecServingState(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { return v, nil } diff --git a/google-beta/services/cloudrun/resource_cloud_run_service_test.go b/google-beta/services/cloudrun/resource_cloud_run_service_test.go index e939a372af..75a310b556 100644 --- a/google-beta/services/cloudrun/resource_cloud_run_service_test.go +++ b/google-beta/services/cloudrun/resource_cloud_run_service_test.go @@ -1010,3 +1010,126 @@ resource "google_cloud_run_service" "default" { } `, name, project) } + +func TestAccCloudRunService_csiVolume(t *testing.T) { + t.Parallel() + + project := envvar.GetTestProjectFromEnv() + name := "tftest-cloudrun-" + acctest.RandString(t, 6) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccCloudRunService_cloudRunServiceWithEmptyDirVolume(name, project), + }, + { + ResourceName: "google_cloud_run_service.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metadata.0.resource_version", "metadata.0.annotations", "metadata.0.labels", "metadata.0.terraform_labels", "status.0.conditions"}, + }, + { + Config: testAccCloudRunService_cloudRunServiceUpdateWithGcsVolume(name, project), + }, + { + ResourceName: "google_cloud_run_service.default", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metadata.0.resource_version", "metadata.0.annotations", "metadata.0.labels", "metadata.0.terraform_labels", "status.0.conditions"}, + }, + }, + }) +} + +func testAccCloudRunService_cloudRunServiceWithEmptyDirVolume(name, project string) string { + return fmt.Sprintf(` +resource "google_cloud_run_service" "default" { + provider = google-beta + name = "%s" + location = "us-central1" + + metadata { + namespace = "%s" + annotations = { + generated-by = "magic-modules" + "run.googleapis.com/launch-stage" = "BETA" + } + } + + template { + spec { + containers { + image = "gcr.io/cloudrun/hello" + volume_mounts { + name = "vol1" + mount_path = "/mnt/vol1" + } + } + volumes { + name = "vol1" + empty_dir { size_limit = "256Mi" } + } + } + } + + lifecycle { + ignore_changes = [ + metadata.0.annotations, + ] + } +} +`, name, project) +} + +func testAccCloudRunService_cloudRunServiceUpdateWithGcsVolume(name, project string) string { + return fmt.Sprintf(` +resource "google_cloud_run_service" "default" { + provider = google-beta + name = "%s" + location = "us-central1" + + metadata { + namespace = "%s" + annotations = { + generated-by = "magic-modules" + "run.googleapis.com/launch-stage" = "BETA" + } + } + + template { + metadata { + annotations = { + "run.googleapis.com/execution-environment" = "gen2" + } + } + spec { + containers { + image = "gcr.io/cloudrun/hello" + volume_mounts { + name = "vol1" + mount_path = "/mnt/vol1" + } + } + volumes { + name = "vol1" + csi { + driver = "gcsfuse.run.googleapis.com" + read_only = true + volume_attributes = { + bucketName = "gcp-public-data-landsat" + } + } + } + } + } + + lifecycle { + ignore_changes = [ + metadata.0.annotations, + ] + } +} +`, name, project) +} diff --git a/website/docs/r/cloud_run_service.html.markdown b/website/docs/r/cloud_run_service.html.markdown index 60a05dd7c9..7a68af84d0 100644 --- a/website/docs/r/cloud_run_service.html.markdown +++ b/website/docs/r/cloud_run_service.html.markdown @@ -812,6 +812,11 @@ The following arguments are supported: Ephemeral storage which can be backed by real disks (HD, SSD), network storage or memory (i.e. tmpfs). For now only in memory (tmpfs) is supported. It is ephemeral in the sense that when the sandbox is taken down, the data is destroyed with it (it does not persist across sandbox runs). Structure is [documented below](#nested_empty_dir). +* `csi` - + (Optional, [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html)) + A filesystem specified by the Container Storage Interface (CSI). + Structure is [documented below](#nested_csi). + The `secret` block supports: @@ -875,6 +880,25 @@ 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 `csi` block supports: + +* `driver` - + (Required) + Unique name representing the type of file system to be created. Cloud Run supports the following values: + * gcsfuse.run.googleapis.com: Mount a Google Cloud Storage bucket using GCSFuse. This driver requires the + run.googleapis.com/execution-environment annotation to be set to "gen2" and + run.googleapis.com/launch-stage set to "BETA" or "ALPHA". + +* `read_only` - + (Optional) + If true, all mounts created from this volume will be read-only. + +* `volume_attributes` - + (Optional) + Driver-specific attributes. The following options are supported for available drivers: + * gcsfuse.run.googleapis.com + * bucketName: The name of the Cloud Storage Bucket that backs this volume. The Cloud Run Service identity must have access to this bucket. + - - -