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
47 changes: 39 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,15 @@ 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.
// This field is read-only. Object retention can be enabled only by creating
// a bucket with SetObjectRetention set to true on the BucketHandle. It
// cannot be modified once the bucket is created.
// ObjectRetention cannot be configured or reported through the gRPC API.
ObjectRetentionMode string
}

// BucketPolicyOnly is an alias for UniformBucketLevelAccess.
Expand Down Expand Up @@ -757,6 +768,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 +783,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 @@ -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 will enable object retention
// on bucket creation. To enable object retention, you must use the returned
// handle to create the bucket. This has no effect on an already existing 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,13 @@ func toRetentionPolicyFromProto(rp *storagepb.Bucket_RetentionPolicy) *Retention
}
}

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
13 changes: 11 additions & 2 deletions 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 All @@ -60,7 +60,7 @@ type storageClient interface {

DeleteObject(ctx context.Context, bucket, object string, gen int64, conds *Conditions, opts ...storageOption) error
GetObject(ctx context.Context, bucket, object string, gen int64, encryptionKey []byte, conds *Conditions, opts ...storageOption) (*ObjectAttrs, error)
UpdateObject(ctx context.Context, bucket, object string, uattrs *ObjectAttrsToUpdate, gen int64, encryptionKey []byte, conds *Conditions, opts ...storageOption) (*ObjectAttrs, error)
UpdateObject(ctx context.Context, params *updateObjectParams, opts ...storageOption) (*ObjectAttrs, error)
tritone marked this conversation as resolved.
Show resolved Hide resolved

// Default Object ACL methods.

Expand Down Expand Up @@ -291,6 +291,15 @@ type newRangeReaderParams struct {
readCompressed bool // Use accept-encoding: gzip. Only works for HTTP currently.
}

type updateObjectParams struct {
bucket, object string
uattrs *ObjectAttrsToUpdate
gen int64
encryptionKey []byte
conds *Conditions
overrideRetention *bool
}

type composeObjectRequest struct {
dstBucket string
dstObject destinationObject
Expand Down
Loading
Loading