Skip to content

Commit

Permalink
Adds support for Soft Delete feature, which allows setting soft delet…
Browse files Browse the repository at this point in the history
…e policy on 'google_storage_bucket' resource.
  • Loading branch information
kautikdk committed Mar 13, 2024
1 parent 3907a64 commit 77c08d0
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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).

<a name="nested_lifecycle_rule"></a>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).
Expand Down Expand Up @@ -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).

<a name="nested_soft_delete_policy"></a>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
Expand Down

0 comments on commit 77c08d0

Please sign in to comment.