From b4299eeff348ed61ecd680e4203d3bff846db839 Mon Sep 17 00:00:00 2001 From: noahdietz Date: Mon, 21 Mar 2022 13:49:37 -0700 Subject: [PATCH 1/2] chore(storage): add Bucket Get & Delete implementations --- storage/bucket.go | 26 +++++++++++++++++++++ storage/client_test.go | 53 +++++++++++++++++++++++++++++++++++++++++- storage/grpc_client.go | 48 ++++++++++++++++++++++++++++++++++++-- storage/http_client.go | 41 ++++++++++++++++++++++++++++++-- 4 files changed, 163 insertions(+), 5 deletions(-) diff --git a/storage/bucket.go b/storage/bucket.go index ab8cce2601b7..8e3d27ed82f5 100644 --- a/storage/bucket.go +++ b/storage/bucket.go @@ -1284,6 +1284,32 @@ func applyBucketConds(method string, conds *BucketConditions, call interface{}) return nil } +// applyBucketConds modifies the provided request message using the conditions +// in conds. msg is a protobuf Message that has fields if_metageneration_match +// and if_metageneration_not_match. +func applyBucketCondsProto(method string, conds *BucketConditions, msg proto.Message) error { + rmsg := msg.ProtoReflect() + + if conds == nil { + return nil + } + if err := conds.validate(method); err != nil { + return err + } + + switch { + case conds.MetagenerationMatch != 0: + if !setConditionProtoField(rmsg, "if_metageneration_match", conds.MetagenerationMatch) { + return fmt.Errorf("storage: %s: ifMetagenerationMatch not supported", method) + } + case conds.MetagenerationNotMatch != 0: + if !setConditionProtoField(rmsg, "if_metageneration_not_match", conds.MetagenerationNotMatch) { + return fmt.Errorf("storage: %s: ifMetagenerationNotMatch not supported", method) + } + } + return nil +} + func (rp *RetentionPolicy) toRawRetentionPolicy() *raw.BucketRetentionPolicy { if rp == nil { return nil diff --git a/storage/client_test.go b/storage/client_test.go index d627b19280a9..37d0eb0e64e0 100644 --- a/storage/client_test.go +++ b/storage/client_test.go @@ -37,7 +37,7 @@ func TestCreateBucketEmulated(t *testing.T) { } got, err := client.CreateBucket(context.Background(), project, want) if err != nil { - t.Fatal(err) + t.Fatalf("%s, %v", transport, err) } want.Location = "US" if diff := cmp.Diff(got.Name, want.Name); diff != "" { @@ -49,6 +49,57 @@ func TestCreateBucketEmulated(t *testing.T) { } } +func TestDeleteBucketEmulated(t *testing.T) { + checkEmulatorEnvironment(t) + + for transport, client := range emulatorClients { + project := fmt.Sprintf("%s-project", transport) + b := &BucketAttrs{ + Name: fmt.Sprintf("%s-bucket-%d", transport, time.Now().Nanosecond()), + } + // Create the bucket that will be deleted. + _, err := client.CreateBucket(context.Background(), project, b) + if err != nil { + // Flag the error and continue so the Delete is skipped and the next + // transport can run its test. + t.Errorf("%s: %v", transport, err) + continue + } + // Delete the bucket that was just created. + err = client.DeleteBucket(context.Background(), b.Name, nil) + if err != nil { + t.Errorf("%s: %v", transport, err) + } + } +} + +func TestGetBucketEmulated(t *testing.T) { + checkEmulatorEnvironment(t) + + for transport, client := range emulatorClients { + project := fmt.Sprintf("%s-project", transport) + want := &BucketAttrs{ + Name: fmt.Sprintf("%s-bucket-%d", transport, time.Now().Nanosecond()), + } + // Create the bucket that will be retrieved. + _, err := client.CreateBucket(context.Background(), project, want) + if err != nil { + // Flag the error and continue so the Get is skipped and the next + // transport can run its test. + t.Errorf("%s: %v", transport, err) + continue + } + got, err := client.GetBucket(context.Background(), want.Name, &BucketConditions{MetagenerationMatch: 1}) + if err != nil { + t.Errorf("%s: %v", transport, err) + continue + } + if diff := cmp.Diff(got.Name, want.Name); diff != "" { + t.Errorf("%s: got(-),want(+):\n%s", transport, diff) + } + } +} + func initEmulatorClients() func() error { noopCloser := func() error { return nil } if !isEmulatorEnvironmentSet() { diff --git a/storage/grpc_client.go b/storage/grpc_client.go index 574c8a57c944..f85747e252f8 100644 --- a/storage/grpc_client.go +++ b/storage/grpc_client.go @@ -23,6 +23,8 @@ import ( iampb "google.golang.org/genproto/googleapis/iam/v1" storagepb "google.golang.org/genproto/googleapis/storage/v2" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) const ( @@ -144,10 +146,52 @@ func (c *grpcStorageClient) ListBuckets(ctx context.Context, project string, opt // Bucket methods. func (c *grpcStorageClient) DeleteBucket(ctx context.Context, bucket string, conds *BucketConditions, opts ...storageOption) error { - return errMethodNotSupported + s := callSettings(c.settings, opts...) + req := &storagepb.DeleteBucketRequest{ + Name: bucketResourceName( /* project */ "_", bucket), + } + if err := applyBucketCondsProto("grpcStorageClient.DeleteBucket", conds, req); err != nil { + return err + } + if s.userProject != "" { + req.CommonRequestParams = &storagepb.CommonRequestParams{ + UserProject: toProjectResource(s.userProject), + } + } + + return run(ctx, func() error { + return c.raw.DeleteBucket(ctx, req, s.gax...) + }, s.retry, s.idempotent) } + func (c *grpcStorageClient) GetBucket(ctx context.Context, bucket string, conds *BucketConditions, opts ...storageOption) (*BucketAttrs, error) { - return nil, errMethodNotSupported + s := callSettings(c.settings, opts...) + req := &storagepb.GetBucketRequest{ + Name: bucketResourceName( /* project */ "_", bucket), + } + if err := applyBucketCondsProto("grpcStorageClient.GetBucket", conds, req); err != nil { + return nil, err + } + if s.userProject != "" { + req.CommonRequestParams = &storagepb.CommonRequestParams{ + UserProject: toProjectResource(s.userProject), + } + } + + var battrs *BucketAttrs + err := run(ctx, func() error { + res, err := c.raw.GetBucket(ctx, req, s.gax...) + + battrs = newBucketFromProto(res) + + return err + }, s.retry, s.idempotent) + + if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound { + return nil, ErrBucketNotExist + } + + return battrs, err } func (c *grpcStorageClient) UpdateBucket(ctx context.Context, uattrs *BucketAttrsToUpdate, conds *BucketConditions, opts ...storageOption) (*BucketAttrs, error) { return nil, errMethodNotSupported diff --git a/storage/http_client.go b/storage/http_client.go index 1adf383390e8..f494b74fb164 100644 --- a/storage/http_client.go +++ b/storage/http_client.go @@ -23,6 +23,8 @@ import ( "strings" "golang.org/x/oauth2/google" + "golang.org/x/xerrors" + "google.golang.org/api/googleapi" "google.golang.org/api/option" "google.golang.org/api/option/internaloption" raw "google.golang.org/api/storage/v1" @@ -178,10 +180,45 @@ func (c *httpStorageClient) ListBuckets(ctx context.Context, project string, opt // Bucket methods. func (c *httpStorageClient) DeleteBucket(ctx context.Context, bucket string, conds *BucketConditions, opts ...storageOption) error { - return errMethodNotSupported + s := callSettings(c.settings, opts...) + req := c.raw.Buckets.Delete(bucket) + setClientHeader(req.Header()) + if err := applyBucketConds("httpStorageClient.DeleteBucket", conds, req); err != nil { + return err + } + if s.userProject != "" { + req.UserProject(s.userProject) + } + + return run(ctx, func() error { return req.Context(ctx).Do() }, s.retry, s.idempotent) } + func (c *httpStorageClient) GetBucket(ctx context.Context, bucket string, conds *BucketConditions, opts ...storageOption) (*BucketAttrs, error) { - return nil, errMethodNotSupported + s := callSettings(c.settings, opts...) + req := c.raw.Buckets.Get(bucket).Projection("full") + setClientHeader(req.Header()) + err := applyBucketConds("httpStorageClient.GetBucket", conds, req) + if err != nil { + return nil, err + } + if s.userProject != "" { + req.UserProject(s.userProject) + } + + var resp *raw.Bucket + err = run(ctx, func() error { + resp, err = req.Context(ctx).Do() + return err + }, s.retry, s.idempotent) + + var e *googleapi.Error + if ok := xerrors.As(err, &e); ok && e.Code == http.StatusNotFound { + return nil, ErrBucketNotExist + } + if err != nil { + return nil, err + } + return newBucket(resp) } func (c *httpStorageClient) UpdateBucket(ctx context.Context, uattrs *BucketAttrsToUpdate, conds *BucketConditions, opts ...storageOption) (*BucketAttrs, error) { return nil, errMethodNotSupported From e5578d418aaa7ed113e76f1d1befd2ae62888fad Mon Sep 17 00:00:00 2001 From: noahdietz Date: Tue, 22 Mar 2022 16:38:47 -0700 Subject: [PATCH 2/2] use errors instead of xerrors --- storage/http_client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/storage/http_client.go b/storage/http_client.go index f494b74fb164..30735af4e3de 100644 --- a/storage/http_client.go +++ b/storage/http_client.go @@ -16,6 +16,7 @@ package storage import ( "context" + "errors" "fmt" "net/http" "net/url" @@ -23,7 +24,6 @@ import ( "strings" "golang.org/x/oauth2/google" - "golang.org/x/xerrors" "google.golang.org/api/googleapi" "google.golang.org/api/option" "google.golang.org/api/option/internaloption" @@ -212,7 +212,7 @@ func (c *httpStorageClient) GetBucket(ctx context.Context, bucket string, conds }, s.retry, s.idempotent) var e *googleapi.Error - if ok := xerrors.As(err, &e); ok && e.Code == http.StatusNotFound { + if ok := errors.As(err, &e); ok && e.Code == http.StatusNotFound { return nil, ErrBucketNotExist } if err != nil {