diff --git a/storage/bucket.go b/storage/bucket.go index e69d1e61e28b..d582a60d0e83 100644 --- a/storage/bucket.go +++ b/storage/bucket.go @@ -2123,8 +2123,11 @@ func (p *SoftDeletePolicy) toRawSoftDeletePolicy() *raw.BucketSoftDeletePolicy { return nil } // Excluding read only field EffectiveTime. + // ForceSendFields must be set to send a zero value for RetentionDuration and disable + // soft delete. return &raw.BucketSoftDeletePolicy{ RetentionDurationSeconds: int64(p.RetentionDuration.Seconds()), + ForceSendFields: []string{"RetentionDurationSeconds"}, } } diff --git a/storage/bucket_test.go b/storage/bucket_test.go index de9d2f91426a..fd03c1e41907 100644 --- a/storage/bucket_test.go +++ b/storage/bucket_test.go @@ -172,7 +172,7 @@ func TestBucketAttrsToRawBucket(t *testing.T) { Logging: &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"}, Website: &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"}, Autoclass: &raw.BucketAutoclass{Enabled: true, TerminalStorageClass: "NEARLINE"}, - SoftDeletePolicy: &raw.BucketSoftDeletePolicy{RetentionDurationSeconds: 60 * 60}, + SoftDeletePolicy: &raw.BucketSoftDeletePolicy{RetentionDurationSeconds: 60 * 60, ForceSendFields: []string{"RetentionDurationSeconds"}}, HierarchicalNamespace: &raw.BucketHierarchicalNamespace{Enabled: true}, Lifecycle: &raw.BucketLifecycle{ Rule: []*raw.BucketLifecycleRule{{ @@ -448,7 +448,7 @@ func TestBucketAttrsToUpdateToRawBucket(t *testing.T) { Website: &raw.BucketWebsite{MainPageSuffix: "mps", NotFoundPage: "404"}, StorageClass: "NEARLINE", Autoclass: &raw.BucketAutoclass{Enabled: true, TerminalStorageClass: "ARCHIVE", ForceSendFields: []string{"Enabled"}}, - SoftDeletePolicy: &raw.BucketSoftDeletePolicy{RetentionDurationSeconds: 3600}, + SoftDeletePolicy: &raw.BucketSoftDeletePolicy{RetentionDurationSeconds: 3600, ForceSendFields: []string{"RetentionDurationSeconds"}}, ForceSendFields: []string{"DefaultEventBasedHold", "Lifecycle", "Autoclass"}, } if msg := testutil.Diff(got, want); msg != "" { diff --git a/storage/integration_test.go b/storage/integration_test.go index 31f102be55f6..3dc0b764b197 100644 --- a/storage/integration_test.go +++ b/storage/integration_test.go @@ -4377,7 +4377,23 @@ func TestIntegration_SoftDelete(t *testing.T) { t.Fatalf("effective time of soft delete policy should not be in the past, got: %v, test start: %v", got.EffectiveTime, testStart.UTC()) } - // Update the soft delete policy. + // Create a second bucket with an empty soft delete policy to verify + // that this leads to no retention. + b2 := client.Bucket(prefix + uidSpace.New()) + if err := b2.Create(ctx, testutil.ProjID(), &BucketAttrs{SoftDeletePolicy: &SoftDeletePolicy{}}); err != nil { + t.Fatalf("error creating bucket with soft delete disabled: %v", err) + } + t.Cleanup(func() { h.mustDeleteBucket(b2) }) + + attrs2, err := b2.Attrs(ctx) + if err != nil { + t.Fatalf("Attrs(%q): %v", b2.name, err) + } + if got, expect := attrs2.SoftDeletePolicy.RetentionDuration, time.Duration(0); got != expect { + t.Fatalf("mismatching retention duration; got: %+v, expected: %+v", got, expect) + } + + // Update the soft delete policy of the original bucket. policy.RetentionDuration = time.Hour * 24 * 9 attrs, err = b.Update(ctx, BucketAttrsToUpdate{SoftDeletePolicy: policy})