From 5458442989a127dcb427c8049633ba19e1c370bf Mon Sep 17 00:00:00 2001 From: kautikdk <144651627+kautikdk@users.noreply.github.com> Date: Tue, 19 Mar 2024 18:04:35 +0000 Subject: [PATCH] Adds support for Soft Delete feature, which allows setting soft delete policy on 'google_storage_bucket' resource. (#10171) --- .../storage/resource_storage_bucket.go.erb | 63 +++++++++++++++- .../resource_storage_bucket_test.go.erb | 74 +++++++++++++++++++ .../docs/r/storage_bucket.html.markdown | 8 ++ 3 files changed, 144 insertions(+), 1 deletion(-) diff --git a/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.erb b/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.erb index ed7422389d82..fc17d08790b8 100644 --- a/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.erb +++ b/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.erb @@ -482,6 +482,28 @@ func ResourceStorageBucket() *schema.Resource { Computed: true, Description: `Prevents public access to a bucket.`, }, + "soft_delete_policy": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Computed: true, + Description: `The bucket's soft delete policy, which defines the period of time that soft-deleted objects will be retained, and cannot be permanently deleted. If it is not provided, by default Google Cloud Storage sets this to default soft delete policy`, + Elem : &schema.Resource{ + Schema: map[string]*schema.Schema{ + "retention_duration_seconds": { + Type: schema.TypeInt, + Default: 604800, + Optional: true, + Description: `The duration in seconds that soft-deleted objects in the bucket will be retained and cannot be permanently deleted. Default value is 604800.`, + }, + "effective_time": { + Type: schema.TypeString, + Computed: true, + Description: `Server-determined value that indicates the time from which the policy, or one with a greater retention, was effective. This value is in RFC 3339 format.`, + }, + }, + }, + }, }, UseJSONNumber: true, } @@ -612,6 +634,10 @@ func resourceStorageBucketCreate(d *schema.ResourceData, meta interface{}) error sb.Rpo = v.(string) } + if v, ok := d.GetOk("soft_delete_policy"); ok { + sb.SoftDeletePolicy = expandBucketSoftDeletePolicy(v.([]interface{})) + } + var res *storage.Bucket err = transport_tpg.Retry(transport_tpg.RetryOptions{ @@ -784,6 +810,12 @@ func resourceStorageBucketUpdate(d *schema.ResourceData, meta interface{}) error } } + if d.HasChange("soft_delete_policy") { + if v, ok := d.GetOk("soft_delete_policy"); ok { + sb.SoftDeletePolicy = expandBucketSoftDeletePolicy(v.([]interface{})) + } + } + res, err := config.NewStorageClient(userAgent).Buckets.Patch(d.Get("name").(string), sb).Do() if err != nil { return err @@ -1166,6 +1198,32 @@ func flattenBucketObjectRetention(bucketObjectRetention *storage.BucketObjectRet return false } +func expandBucketSoftDeletePolicy(configured interface{}) *storage.BucketSoftDeletePolicy{ + configuredSoftDeletePolicies := configured.([]interface{}) + if len(configuredSoftDeletePolicies) == 0 { + return nil + } + configuredSoftDeletePolicy := configuredSoftDeletePolicies[0].(map[string]interface{}) + softDeletePolicy := &storage.BucketSoftDeletePolicy{ + RetentionDurationSeconds: int64(configuredSoftDeletePolicy["retention_duration_seconds"].(int)), + } + softDeletePolicy.ForceSendFields=append(softDeletePolicy.ForceSendFields,"RetentionDurationSeconds") + return softDeletePolicy +} + +func flattenBucketSoftDeletePolicy(softDeletePolicy *storage.BucketSoftDeletePolicy) []map[string]interface{} { + policies := make([]map[string]interface{}, 0, 1) + if softDeletePolicy == nil { + return policies + } + policy := map[string]interface{}{ + "retention_duration_seconds": softDeletePolicy.RetentionDurationSeconds, + "effective_time": softDeletePolicy.EffectiveTime, + } + policies = append(policies, policy) + return policies +} + func expandBucketVersioning(configured interface{}) *storage.BucketVersioning { versionings := configured.([]interface{}) if len(versionings) == 0 { @@ -1689,7 +1747,7 @@ func setStorageBucket(d *schema.ResourceData, config *transport_tpg.Config, res } // lifecycle_rule contains terraform only variable no_age. // Passing config("d") to flattener function to set no_age separately. - if err := d.Set("lifecycle_rule", flattenBucketLifecycle(d,res.Lifecycle)); err != nil { + if err := d.Set("lifecycle_rule", flattenBucketLifecycle(d, res.Lifecycle)); err != nil { return fmt.Errorf("Error setting lifecycle_rule: %s", err) } if err := tpgresource.SetLabels(res.Labels, d, "labels"); err != nil { @@ -1717,6 +1775,9 @@ func setStorageBucket(d *schema.ResourceData, config *transport_tpg.Config, res return fmt.Errorf("Error setting RPO setting : %s", err) } } + if err := d.Set("soft_delete_policy", flattenBucketSoftDeletePolicy(res.SoftDeletePolicy)); err != nil { + return fmt.Errorf("Error setting soft_delete_policy: %s", err) + } if res.IamConfiguration != nil && res.IamConfiguration.UniformBucketLevelAccess != nil { if err := d.Set("uniform_bucket_level_access", res.IamConfiguration.UniformBucketLevelAccess.Enabled); err != nil { return fmt.Errorf("Error setting uniform_bucket_level_access: %s", err) diff --git a/mmv1/third_party/terraform/services/storage/resource_storage_bucket_test.go.erb b/mmv1/third_party/terraform/services/storage/resource_storage_bucket_test.go.erb index f62447cb399e..22ef2baecef6 100644 --- a/mmv1/third_party/terraform/services/storage/resource_storage_bucket_test.go.erb +++ b/mmv1/third_party/terraform/services/storage/resource_storage_bucket_test.go.erb @@ -1338,6 +1338,66 @@ func TestAccStorageBucket_retentionPolicyLocked(t *testing.T) { }) } +func TestAccStorageBucket_SoftDeletePolicy(t *testing.T) { + t.Parallel() + + var bucket storage.Bucket + bucketName := fmt.Sprintf("tf-test-acc-bucket-%d", acctest.RandInt(t)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccStorageBucketDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageBucket_basic(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + t, "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "soft_delete_policy.0.retention_duration_seconds", "604800"), + ), + }, + { + ResourceName: "google_storage_bucket.bucket", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"force_destroy"}, + }, + { + Config: testAccStorageBucket_SoftDeletePolicy(bucketName,7776000), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + t, "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "soft_delete_policy.0.retention_duration_seconds", "7776000"), + ), + }, + { + ResourceName: "google_storage_bucket.bucket", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"force_destroy"}, + }, + { + Config: testAccStorageBucket_SoftDeletePolicy(bucketName,0), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + t, "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "soft_delete_policy.0.retention_duration_seconds", "0"), + ), + }, + { + ResourceName: "google_storage_bucket.bucket", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"force_destroy"}, + }, + }, + }) +} + func testAccCheckStorageBucketExists(t *testing.T, n string, bucketName string, bucket *storage.Bucket) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -2264,6 +2324,20 @@ resource "google_storage_bucket" "bucket" { `, bucketName) } +func testAccStorageBucket_SoftDeletePolicy(bucketName string, duration int) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + location = "US" + force_destroy = true + + soft_delete_policy { + retention_duration_seconds = %d + } +} +`, bucketName, duration) +} + func testAccStorageBucket_websiteNoAttributes(bucketName string) string { return fmt.Sprintf(` resource "google_storage_bucket" "website" { diff --git a/mmv1/third_party/terraform/website/docs/r/storage_bucket.html.markdown b/mmv1/third_party/terraform/website/docs/r/storage_bucket.html.markdown index 2389ac04bf55..58e1e1cf5056 100644 --- a/mmv1/third_party/terraform/website/docs/r/storage_bucket.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/storage_bucket.html.markdown @@ -133,6 +133,8 @@ The following arguments are supported: * `custom_placement_config` - (Optional) The bucket's custom location configuration, which specifies the individual regions that comprise a dual-region bucket. If the bucket is designated a single or multi-region, the parameters are empty. Structure is [documented below](#nested_custom_placement_config). +* `soft_delete_policy` - (Optional, Computed) The bucket's soft delete policy, which defines the period of time that soft-deleted objects will be retained, and cannot be permanently deleted. If the block is not provided, Server side value will be kept which means removal of block won't generate any terraform change. Structure is [documented below](#nested_soft_delete_policy). + The `lifecycle_rule` block supports: * `action` - (Required) The Lifecycle Rule's action configuration. A single block of this type is supported. Structure is [documented below](#nested_action). @@ -233,6 +235,12 @@ The following arguments are supported: * `data_locations` - (Required) The list of individual regions that comprise a dual-region bucket. See [Cloud Storage bucket locations](https://cloud.google.com/storage/docs/dual-regions#availability) for a list of acceptable regions. **Note**: If any of the data_locations changes, it will [recreate the bucket](https://cloud.google.com/storage/docs/locations#key-concepts). +The `soft_delete_policy` block supports: + +* `retention_duration_seconds` - (Optional, Default: 604800) The duration in seconds that soft-deleted objects in the bucket will be retained and cannot be permanently deleted. Default value is 604800. The value must be in between 604800(7 days) and 7776000(90 days). **Note**: To disable the soft delete policy on a bucket, This field must be set to 0. + +* `effective_time` - (Computed) Server-determined value that indicates the time from which the policy, or one with a greater retention, was effective. This value is in RFC 3339 format. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are