Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(storage): add object retention feature #9072

Merged
merged 11 commits into from
Dec 13, 2023
56 changes: 48 additions & 8 deletions storage/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,14 @@ import (
// BucketHandle provides operations on a Google Cloud Storage bucket.
// Use Client.Bucket to get a handle.
type BucketHandle struct {
c *Client
name string
acl ACLHandle
defaultObjectACL ACLHandle
conds *BucketConditions
userProject string // project for Requester Pays buckets
retry *retryConfig
c *Client
name string
acl ACLHandle
defaultObjectACL ACLHandle
conds *BucketConditions
userProject string // project for Requester Pays buckets
retry *retryConfig
enableObjectRetention *bool
}

// Bucket returns a BucketHandle, which provides operations on the named bucket.
Expand Down Expand Up @@ -85,7 +86,8 @@ func (b *BucketHandle) Create(ctx context.Context, projectID string, attrs *Buck
defer func() { trace.EndSpan(ctx, err) }()

o := makeStorageOpts(true, b.retry, b.userProject)
if _, err := b.c.tc.CreateBucket(ctx, projectID, b.name, attrs, o...); err != nil {

if _, err := b.c.tc.CreateBucket(ctx, projectID, b.name, attrs, b.enableObjectRetention, o...); err != nil {
return err
}
return nil
Expand Down Expand Up @@ -462,6 +464,14 @@ type BucketAttrs struct {
// allows for the automatic selection of the best storage class
// based on object access patterns.
Autoclass *Autoclass

// ObjectRetentionMode reports whether individual objects in the bucket can
// be configured with a retention policy. An empty value means that object
// retention is disabled. Object retention is not enabled default.
// This field is read-only. Object retention can be enabled only on bucket
tritone marked this conversation as resolved.
Show resolved Hide resolved
// creation on a bucket handle with SetObjectRetention set to true.
// ObjectRetention cannot be configured or reported through the gRPC API.
ObjectRetentionMode string
}

// BucketPolicyOnly is an alias for UniformBucketLevelAccess.
Expand Down Expand Up @@ -757,6 +767,7 @@ func newBucket(b *raw.Bucket) (*BucketAttrs, error) {
if err != nil {
return nil, err
}

return &BucketAttrs{
Name: b.Name,
Location: b.Location,
Expand All @@ -771,6 +782,7 @@ func newBucket(b *raw.Bucket) (*BucketAttrs, error) {
RequesterPays: b.Billing != nil && b.Billing.RequesterPays,
Lifecycle: toLifecycle(b.Lifecycle),
RetentionPolicy: rp,
ObjectRetentionMode: toBucketObjectRetention(b.ObjectRetention),
CORS: toCORS(b.Cors),
Encryption: toBucketEncryption(b.Encryption),
Logging: toBucketLogging(b.Logging),
Expand Down Expand Up @@ -864,6 +876,7 @@ func (b *BucketAttrs) toRawBucket() *raw.Bucket {
Billing: bb,
Lifecycle: toRawLifecycle(b.Lifecycle),
RetentionPolicy: b.RetentionPolicy.toRawRetentionPolicy(),
ObjectRetention: toRawBucketObjectRetention(b.ObjectRetentionMode),
Cors: toRawCORS(b.CORS),
Encryption: b.Encryption.toRawBucketEncryption(),
Logging: b.Logging.toRawBucketLogging(),
Expand Down Expand Up @@ -1348,6 +1361,17 @@ func (b *BucketHandle) LockRetentionPolicy(ctx context.Context) error {
return b.c.tc.LockBucketRetentionPolicy(ctx, b.name, b.conds, o...)
}

// SetObjectRetention returns a new BucketHandle that allows enabling object
tritone marked this conversation as resolved.
Show resolved Hide resolved
// retention on bucket creation. To enable object retention, you must use this
// handle with ObjectRetentionMode set on the BucketAttrs to create the bucket.
// ObjectRetention is not enabled by default.
// ObjectRetention cannot be configured through the gRPC API.
func (b *BucketHandle) SetObjectRetention(enable bool) *BucketHandle {
b2 := *b
b2.enableObjectRetention = &enable
return &b2
}

// applyBucketConds modifies the provided call using the conditions in conds.
// call is something that quacks like a *raw.WhateverCall.
func applyBucketConds(method string, conds *BucketConditions, call interface{}) error {
Expand Down Expand Up @@ -1447,6 +1471,22 @@ func toRetentionPolicyFromProto(rp *storagepb.Bucket_RetentionPolicy) *Retention
}
}

func toRawBucketObjectRetention(retentionMode string) *raw.BucketObjectRetention {
if retentionMode == "" {
return nil
}
return &raw.BucketObjectRetention{
Mode: retentionMode,
}
}

func toBucketObjectRetention(or *raw.BucketObjectRetention) string {
if or == nil {
return ""
}
return or.Mode
}

func toRawCORS(c []CORS) []*raw.BucketCors {
var out []*raw.BucketCors
for _, v := range c {
Expand Down
8 changes: 6 additions & 2 deletions storage/bucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,9 @@ func TestNewBucket(t *testing.T) {
RetentionPeriod: 3,
EffectiveTime: aTime.Format(time.RFC3339),
},
ObjectRetention: &raw.BucketObjectRetention{
tritone marked this conversation as resolved.
Show resolved Hide resolved
Mode: "Enabled",
},
IamConfiguration: &raw.BucketIamConfiguration{
BucketPolicyOnly: &raw.BucketIamConfigurationBucketPolicyOnly{
Enabled: true,
Expand Down Expand Up @@ -686,6 +689,7 @@ func TestNewBucket(t *testing.T) {
EffectiveTime: aTime,
RetentionPeriod: 3 * time.Second,
},
ObjectRetentionMode: "Enabled",
BucketPolicyOnly: BucketPolicyOnly{Enabled: true, LockedTime: aTime},
UniformBucketLevelAccess: UniformBucketLevelAccess{Enabled: true, LockedTime: aTime},
CORS: []CORS{
Expand Down Expand Up @@ -714,7 +718,7 @@ func TestNewBucket(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if diff := testutil.Diff(got, want); diff != "" {
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("got=-, want=+:\n%s", diff)
}
}
Expand Down Expand Up @@ -817,7 +821,7 @@ func TestNewBucketFromProto(t *testing.T) {
},
}
got := newBucketFromProto(pb)
if diff := testutil.Diff(got, want); diff != "" {
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("got=-, want=+:\n%s", diff)
}
}
Expand Down
2 changes: 1 addition & 1 deletion storage/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type storageClient interface {
// Top-level methods.

GetServiceAccount(ctx context.Context, project string, opts ...storageOption) (string, error)
CreateBucket(ctx context.Context, project, bucket string, attrs *BucketAttrs, opts ...storageOption) (*BucketAttrs, error)
CreateBucket(ctx context.Context, project, bucket string, attrs *BucketAttrs, enableObjectRetention *bool, opts ...storageOption) (*BucketAttrs, error)
ListBuckets(ctx context.Context, project string, opts ...storageOption) *BucketIterator
Close() error

Expand Down
50 changes: 25 additions & 25 deletions storage/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestCreateBucketEmulated(t *testing.T) {
LogBucket: bucket,
},
}
got, err := client.CreateBucket(context.Background(), project, want.Name, want)
got, err := client.CreateBucket(context.Background(), project, want.Name, want, nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -63,7 +63,7 @@ func TestDeleteBucketEmulated(t *testing.T) {
Name: bucket,
}
// Create the bucket that will be deleted.
_, err := client.CreateBucket(context.Background(), project, b.Name, b)
_, err := client.CreateBucket(context.Background(), project, b.Name, b, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand All @@ -81,7 +81,7 @@ func TestGetBucketEmulated(t *testing.T) {
Name: bucket,
}
// Create the bucket that will be retrieved.
_, err := client.CreateBucket(context.Background(), project, want.Name, want)
_, err := client.CreateBucket(context.Background(), project, want.Name, want, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand All @@ -101,7 +101,7 @@ func TestUpdateBucketEmulated(t *testing.T) {
Name: bucket,
}
// Create the bucket that will be updated.
_, err := client.CreateBucket(context.Background(), project, bkt.Name, bkt)
_, err := client.CreateBucket(context.Background(), project, bkt.Name, bkt, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand Down Expand Up @@ -194,7 +194,7 @@ func TestGetSetTestIamPolicyEmulated(t *testing.T) {
transportClientTest(t, func(t *testing.T, project, bucket string, client storageClient) {
battrs, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand Down Expand Up @@ -225,7 +225,7 @@ func TestDeleteObjectEmulated(t *testing.T) {
// Populate test object that will be deleted.
_, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand All @@ -252,7 +252,7 @@ func TestGetObjectEmulated(t *testing.T) {
// Populate test object.
_, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand Down Expand Up @@ -282,7 +282,7 @@ func TestRewriteObjectEmulated(t *testing.T) {
// Populate test object.
_, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand Down Expand Up @@ -327,7 +327,7 @@ func TestUpdateObjectEmulated(t *testing.T) {
// Populate test object.
_, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand Down Expand Up @@ -394,7 +394,7 @@ func TestListObjectsEmulated(t *testing.T) {
// Populate test data.
_, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand Down Expand Up @@ -452,7 +452,7 @@ func TestListObjectsWithPrefixEmulated(t *testing.T) {
// Populate test data.
_, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand Down Expand Up @@ -516,7 +516,7 @@ func TestListBucketsEmulated(t *testing.T) {
}
// Create the buckets that will be listed.
for _, b := range want {
_, err := client.CreateBucket(context.Background(), project, b.Name, b)
_, err := client.CreateBucket(context.Background(), project, b.Name, b, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand Down Expand Up @@ -557,7 +557,7 @@ func TestListBucketACLsEmulated(t *testing.T) {
PredefinedACL: "publicRead",
}
// Create the bucket that will be retrieved.
if _, err := client.CreateBucket(ctx, project, attrs.Name, attrs); err != nil {
if _, err := client.CreateBucket(ctx, project, attrs.Name, attrs, nil); err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}

Expand All @@ -579,7 +579,7 @@ func TestUpdateBucketACLEmulated(t *testing.T) {
PredefinedACL: "authenticatedRead",
}
// Create the bucket that will be retrieved.
if _, err := client.CreateBucket(ctx, project, attrs.Name, attrs); err != nil {
if _, err := client.CreateBucket(ctx, project, attrs.Name, attrs, nil); err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
var listAcls []ACLRule
Expand Down Expand Up @@ -615,7 +615,7 @@ func TestDeleteBucketACLEmulated(t *testing.T) {
PredefinedACL: "publicRead",
}
// Create the bucket that will be retrieved.
if _, err := client.CreateBucket(ctx, project, attrs.Name, attrs); err != nil {
if _, err := client.CreateBucket(ctx, project, attrs.Name, attrs, nil); err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
// Assert bucket has two BucketACL entities, including project owner and predefinedACL.
Expand Down Expand Up @@ -649,7 +649,7 @@ func TestDefaultObjectACLCRUDEmulated(t *testing.T) {
PredefinedDefaultObjectACL: "publicRead",
}
// Create the bucket that will be retrieved.
if _, err := client.CreateBucket(ctx, project, attrs.Name, attrs); err != nil {
if _, err := client.CreateBucket(ctx, project, attrs.Name, attrs, nil); err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
// Assert bucket has 2 DefaultObjectACL entities, including project owner and PredefinedDefaultObjectACL.
Expand Down Expand Up @@ -695,7 +695,7 @@ func TestObjectACLCRUDEmulated(t *testing.T) {
ctx := context.Background()
_, err := client.CreateBucket(ctx, project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("CreateBucket: %v", err)
}
Expand Down Expand Up @@ -749,7 +749,7 @@ func TestOpenReaderEmulated(t *testing.T) {
// Populate test data.
_, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand Down Expand Up @@ -798,7 +798,7 @@ func TestOpenWriterEmulated(t *testing.T) {
// Populate test data.
_, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand Down Expand Up @@ -861,7 +861,7 @@ func TestListNotificationsEmulated(t *testing.T) {
ctx := context.Background()
_, err := client.CreateBucket(ctx, project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand Down Expand Up @@ -889,7 +889,7 @@ func TestCreateNotificationEmulated(t *testing.T) {
ctx := context.Background()
_, err := client.CreateBucket(ctx, project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand All @@ -915,7 +915,7 @@ func TestDeleteNotificationEmulated(t *testing.T) {
ctx := context.Background()
_, err := client.CreateBucket(ctx, project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand Down Expand Up @@ -987,7 +987,7 @@ func TestLockBucketRetentionPolicyEmulated(t *testing.T) {
},
}
// Create the bucket that will be locked.
_, err := client.CreateBucket(context.Background(), project, b.Name, b)
_, err := client.CreateBucket(context.Background(), project, b.Name, b, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand All @@ -1013,7 +1013,7 @@ func TestComposeEmulated(t *testing.T) {
// Populate test data.
_, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{
Name: bucket,
})
}, nil)
if err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}
Expand Down Expand Up @@ -1178,7 +1178,7 @@ func TestObjectConditionsEmulated(t *testing.T) {
ctx := context.Background()

// Create test bucket
if _, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{Name: bucket}); err != nil {
if _, err := client.CreateBucket(context.Background(), project, bucket, &BucketAttrs{Name: bucket}, nil); err != nil {
t.Fatalf("client.CreateBucket: %v", err)
}

Expand Down
Loading
Loading