diff --git a/gcs/client/client.go b/gcs/client/client.go index dea5424..590992a 100644 --- a/gcs/client/client.go +++ b/gcs/client/client.go @@ -18,15 +18,18 @@ package client import ( "context" + "encoding/json" "errors" "fmt" "io" "log" "os" "strings" + "sync" "time" "golang.org/x/oauth2/google" + "google.golang.org/api/iterator" "cloud.google.com/go/storage" @@ -37,6 +40,15 @@ import ( // client disallow an attempted write operation. var ErrInvalidROWriteOperation = errors.New("the client operates in read only mode. Change 'credentials_source' parameter value ") +// To enforce concurent go routine numbers during delete-recursive operation +const maxConcurrency = 10 + +type BlobProperties struct { + ETag string `json:"etag,omitempty"` + LastModified time.Time `json:"last_modified,omitempty"` + ContentLength int64 `json:"content_length,omitempty"` +} + // GCSBlobstore encapsulates interaction with the GCS blobstore type GCSBlobstore struct { authenticatedGCS *storage.Client @@ -68,6 +80,11 @@ func (client *GCSBlobstore) getObjectHandle(gcs *storage.Client, src string) *st return handle } +func (client *GCSBlobstore) getBucketHandle(gcs *storage.Client) *storage.BucketHandle { + handle := gcs.Bucket(client.config.BucketName) + return handle +} + // New returns a GCSBlobstore configured to operate using the given config // // non-nil error is returned on invalid Client or config. If the configuration @@ -246,21 +263,160 @@ func (client *GCSBlobstore) Sign(id string, action string, expiry time.Duration) } func (client *GCSBlobstore) List(prefix string) ([]string, error) { - return nil, errors.New("not implemented") + if prefix != "" { + log.Printf("Listing objects in bucket %s with prefix '%s'\n", client.config.BucketName, prefix) + } else { + log.Printf("Listing objects in bucket %s\n", client.config.BucketName) + } + if client.readOnly() { + return nil, ErrInvalidROWriteOperation + } + + bh := client.getBucketHandle(client.authenticatedGCS) + + it := bh.Objects(context.Background(), &storage.Query{Prefix: prefix}) + + var names []string + for { + attr, err := it.Next() + if err == iterator.Done { + break + } + + if err != nil { + return nil, err + } + + names = append(names, attr.Name) + } + + return names, nil + } func (client *GCSBlobstore) Copy(srcBlob string, dstBlob string) error { - return errors.New("not implemented") + log.Printf("copying an object from %s to %s\n", srcBlob, dstBlob) + if client.readOnly() { + return ErrInvalidROWriteOperation + } + + srcHandle := client.getObjectHandle(client.authenticatedGCS, srcBlob) + dstHandle := client.getObjectHandle(client.authenticatedGCS, dstBlob) + + _, err := dstHandle.CopierFrom(srcHandle).Run(context.Background()) + if err != nil { + return fmt.Errorf("copying object: %w", err) + } + return nil } func (client *GCSBlobstore) Properties(dest string) error { - return errors.New("not implemented") + log.Printf("Getting properties for object %s/%s\n", client.config.BucketName, dest) + if client.readOnly() { + return ErrInvalidROWriteOperation + } + oh := client.getObjectHandle(client.authenticatedGCS, dest) + attr, err := oh.Attrs(context.Background()) + + if err != nil { + return fmt.Errorf("getting attributes: %w", err) + } + + props := BlobProperties{ + ETag: strings.Trim(attr.Etag, `"`), + LastModified: attr.Updated, + ContentLength: attr.Size, + } + + output, err := json.MarshalIndent(props, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal blob properties: %w", err) + } + + fmt.Println(string(output)) + return nil } func (client *GCSBlobstore) EnsureStorageExists() error { - return errors.New("not implemented") + log.Printf("Ensuring bucket '%s' exists\n", client.config.BucketName) + if client.readOnly() { + return ErrInvalidROWriteOperation + } + ctx := context.Background() + bh := client.getBucketHandle(client.authenticatedGCS) + + _, err := bh.Attrs(ctx) + if errors.Is(err, storage.ErrBucketNotExist) { + battr := &storage.BucketAttrs{Name: client.config.BucketName} + if client.config.StorageClass != "" { + battr.StorageClass = client.config.StorageClass + } + + projectID, err := extractProjectID(ctx, client.config) + if err != nil { + return fmt.Errorf("extracting project ID: %w", err) + } + + err = bh.Create(ctx, projectID, battr) + if err != nil { + return fmt.Errorf("creating bucket: %w", err) + } + return nil + } + if err != nil { + return fmt.Errorf("checking bucket: %w", err) + } + + return nil } func (client *GCSBlobstore) DeleteRecursive(prefix string) error { - return errors.New("not implemented") + if prefix != "" { + log.Printf("Deleting all objects in bucket %s with prefix '%s'\n", + client.config.BucketName, prefix) + } else { + log.Printf("Deleting all objects in bucket %s\n", client.config.BucketName) + } + + if client.readOnly() { + return ErrInvalidROWriteOperation + } + + names, err := client.List(prefix) + if err != nil { + return fmt.Errorf("listing objects: %w", err) + } + + errChan := make(chan error, len(names)) + semaphore := make(chan struct{}, maxConcurrency) + wg := &sync.WaitGroup{} + for _, n := range names { + name := n + wg.Add(1) + go func() { + defer wg.Done() + + semaphore <- struct{}{} + defer func() { <-semaphore }() + + err := client.getObjectHandle(client.authenticatedGCS, name).Delete(context.Background()) + if err != nil && !errors.Is(err, storage.ErrObjectNotExist) { + errChan <- fmt.Errorf("deleting object %s: %w", name, err) + } + }() + } + + wg.Wait() + close(errChan) + + var errs []error + for err := range errChan { + errs = append(errs, err) + } + + if len(errs) > 0 { + return errors.Join(errs...) + } + + return nil } diff --git a/gcs/client/sdk.go b/gcs/client/sdk.go index 77c2249..4eec4fa 100644 --- a/gcs/client/sdk.go +++ b/gcs/client/sdk.go @@ -18,7 +18,9 @@ package client import ( "context" + "encoding/json" "errors" + "fmt" "golang.org/x/oauth2/google" @@ -52,3 +54,37 @@ func newStorageClients(ctx context.Context, cfg *config.GCSCli) (*storage.Client } return authenticatedClient, publicClient, err } + +func extractProjectID(ctx context.Context, cfg *config.GCSCli) (string, error) { + switch cfg.CredentialsSource { + case config.ServiceAccountFileCredentialsSource: + // Parse service account JSON to extract project_id + var serviceAccount struct { + ProjectID string `json:"project_id"` + } + if err := json.Unmarshal([]byte(cfg.ServiceAccountFile), &serviceAccount); err != nil { + return "", fmt.Errorf("parsing service account JSON: %w", err) + } + if serviceAccount.ProjectID == "" { + return "", errors.New("project_id not found in service account JSON") + } + return serviceAccount.ProjectID, nil + + case config.DefaultCredentialsSource: + // Try to get project ID from default credentials + creds, err := google.FindDefaultCredentials(ctx, storage.ScopeFullControl) + if err != nil { + return "", fmt.Errorf("finding default credentials: %w", err) + } + if creds.ProjectID == "" { + return "", errors.New("project_id not found in default credentials") + } + return creds.ProjectID, nil + + case config.NoneCredentialsSource: + return "", errors.New("cannot create bucket with read-only credentials") + + default: + return "", errors.New("unknown credentials_source") + } +} diff --git a/gcs/integration/assertions.go b/gcs/integration/assertions.go index 8410be5..dee74a6 100644 --- a/gcs/integration/assertions.go +++ b/gcs/integration/assertions.go @@ -17,6 +17,7 @@ package integration import ( + "fmt" "os" . "github.com/onsi/gomega" //nolint:staticcheck @@ -46,6 +47,14 @@ func AssertLifecycleWorks(gcsCLIPath string, ctx AssertContext) { Expect(session.ExitCode()).To(BeZero()) Expect(session.Err.Contents()).To(MatchRegexp("File '.*' exists in bucket '.*'")) + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "properties", ctx.GCSFileName) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + output := string(session.Out.Contents()) + Expect(output).To(MatchRegexp(`"etag":\s*".+?"`)) + Expect(output).To(MatchRegexp(`"last_modified":\s*".+?"`)) + Expect(output).To(MatchRegexp(`"content_length":\s*\d+`)) + tmpLocalFileName := "gcscli-download" defer os.Remove(tmpLocalFileName) //nolint:errcheck @@ -66,3 +75,134 @@ func AssertLifecycleWorks(gcsCLIPath string, ctx AssertContext) { Expect(session.ExitCode()).To(Equal(3)) Expect(session.Err.Contents()).To(MatchRegexp("File '.*' does not exist in bucket '.*'")) } + +func AssertDeleteRecursiveWithPrefixLifecycle(gcsCLIPath string, ctx AssertContext) { + storageType := "gcs" + + fileName1 := MakeContentFile(GenerateRandomString()) + fileName2 := MakeContentFile(GenerateRandomString()) + fileName3 := MakeContentFile(GenerateRandomString()) + prefix := fmt.Sprintf("%s-%s/", "test-prefix-delete-recursive", GenerateRandomString(10)) + dstObject1 := fmt.Sprintf("%s%s", prefix, GenerateRandomString()) + dstObject2 := fmt.Sprintf("%s%s", prefix, GenerateRandomString()) + dstObject3 := GenerateRandomString() + defer os.Remove(fileName1) //nolint:errcheck + defer os.Remove(fileName2) //nolint:errcheck + defer os.Remove(fileName3) //nolint:errcheck + + session, err := RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "put", fileName1, dstObject1) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "put", fileName2, dstObject2) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "put", fileName3, dstObject3) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "delete-recursive", prefix) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "exists", dstObject3) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "exists", dstObject1) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(Equal(3)) + Expect(session.Err.Contents()).To(MatchRegexp("File '.*' does not exist in bucket '.*'")) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "exists", dstObject1) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(Equal(3)) + Expect(session.Err.Contents()).To(MatchRegexp("File '.*' does not exist in bucket '.*'")) + + //cleanup artifact + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "delete", dstObject3) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + +} + +func AssertCopyLifecycle(gcsCLIPath string, ctx AssertContext) { + storageType := "gcs" + + dstNameToPut := GenerateRandomString() + dstNameToCopy := GenerateRandomString() + + session, err := RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "put", ctx.ContentFile, dstNameToPut) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "copy", dstNameToPut, dstNameToCopy) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + tmpFileName := "copy-lifecycle" + defer os.Remove(tmpFileName) //nolint:errcheck + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "get", dstNameToCopy, tmpFileName) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + contentGet, err := os.ReadFile(tmpFileName) + Expect(err).ToNot(HaveOccurred()) + Expect(string(contentGet)).To(Equal(ctx.ExpectedString)) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "delete", dstNameToPut) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "delete", dstNameToCopy) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) +} + +func AssertListMultipleWithPrefixLifecycle(gcsCLIPath string, ctx AssertContext) { + storageType := "gcs" + fileName1 := MakeContentFile(GenerateRandomString()) + fileName2 := MakeContentFile(GenerateRandomString()) + fileName3 := MakeContentFile(GenerateRandomString()) + prefix := fmt.Sprintf("%s-%s/", "test-prefix-list", GenerateRandomString(10)) + dstObject1 := fmt.Sprintf("%s%s", prefix, GenerateRandomString()) + dstObject2 := fmt.Sprintf("%s%s", prefix, GenerateRandomString()) + dstObject3 := GenerateRandomString() + defer os.Remove(fileName1) //nolint:errcheck + defer os.Remove(fileName2) //nolint:errcheck + defer os.Remove(fileName3) //nolint:errcheck + + session, err := RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "put", fileName1, dstObject1) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "put", fileName2, dstObject2) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "put", fileName3, dstObject3) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "list", prefix) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + objs := string(session.Out.Contents()) + Expect(objs).To(ContainSubstring(dstObject1)) + Expect(objs).To(ContainSubstring(dstObject2)) + Expect(objs).ToNot(ContainSubstring(dstObject3)) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "delete", dstObject1) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "delete", dstObject2) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "delete", dstObject3) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) +} diff --git a/gcs/integration/gcs_general_test.go b/gcs/integration/gcs_general_test.go index 3beabaf..8fa6569 100644 --- a/gcs/integration/gcs_general_test.go +++ b/gcs/integration/gcs_general_test.go @@ -17,15 +17,16 @@ package integration import ( + context "context" "fmt" "os" + "strings" "syscall" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/cloudfoundry/storage-cli/gcs/client" "github.com/cloudfoundry/storage-cli/gcs/config" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = Describe("Integration", func() { @@ -125,5 +126,73 @@ var _ = Describe("Integration", func() { Expect(session.Err.Contents()).To(ContainSubstring("object doesn't exist")) }, configurations) + + DescribeTable("copying will create same content with different name", func(config *config.GCSCli) { + env.AddConfig(config) + AssertCopyLifecycle(gcsCLIPath, env) + }, configurations) + + DescribeTable("invalid copy should fail", func(config *config.GCSCli) { + env.AddConfig(config) + + session, err := RunGCSCLI(gcsCLIPath, env.ConfigPath, storageType, "copy", "source-object", "dest-object") + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).ToNot(BeZero()) + Expect(string(session.Err.Contents())).To(ContainSubstring("object doesn't exist")) + + }, configurations) + + Context("when bucket is not exist", func() { + DescribeTable("ensure storage exist will create a new bucket", func(cfg *config.GCSCli) { + // create new a newCfg instead of modifying shared cfg accross all tests + newCfg := &config.GCSCli{ + BucketName: strings.ToLower(GenerateRandomString()), + } + + env.AddConfig(newCfg) + + session, err := RunGCSCLI(gcsCLIPath, env.ConfigPath, storageType, "ensure-storage-exists") + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + deleteBucket(context.Background(), newCfg.BucketName, env.ConfigPath) + }, configurations) + }) + + Context("when bucket exists", func() { + DescribeTable("ensure storage exist will not create a new bucket", func(cfg *config.GCSCli) { + env.AddConfig(cfg) + session, err := RunGCSCLI(gcsCLIPath, env.ConfigPath, storageType, "ensure-storage-exists") + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + }, configurations) + }) + + Context("when working with multiple objects", func() { + DescribeTable("recursive deleting will delete only the objects that have same prefix", func(config *config.GCSCli) { + env.AddConfig(config) + AssertDeleteRecursiveWithPrefixLifecycle(gcsCLIPath, env) + }, + configurations) + DescribeTable("list will output only the objects that have same prefix", func(config *config.GCSCli) { + env.AddConfig(config) + AssertListMultipleWithPrefixLifecycle(gcsCLIPath, env) + }, configurations) + + }) + + DescribeTable("delete-recursive is idempotent", func(config *config.GCSCli) { + env.AddConfig(config) + + session, err := RunGCSCLI(gcsCLIPath, env.ConfigPath, storageType, "delete-recursive") + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + session, err = RunGCSCLI(gcsCLIPath, env.ConfigPath, storageType, "delete-recursive") + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + + }, configurations) }) }) diff --git a/gcs/integration/gcs_public_test.go b/gcs/integration/gcs_public_test.go index b64ca36..5efbc5f 100644 --- a/gcs/integration/gcs_public_test.go +++ b/gcs/integration/gcs_public_test.go @@ -93,21 +93,56 @@ var _ = Describe("GCS Public Bucket", func() { session, err := RunGCSCLI(gcsCLIPath, publicEnv.ConfigPath, storageType, "get", setupEnv.GCSFileName, "/dev/null") Expect(err).ToNot(HaveOccurred()) Expect(session.ExitCode()).ToNot(BeZero()) - Expect(session.Err.Contents()).To(ContainSubstring("object doesn't exist")) + Expect(string(session.Err.Contents())).To(ContainSubstring("object doesn't exist")) }) It("fails to put", func() { session, err := RunGCSCLI(gcsCLIPath, publicEnv.ConfigPath, storageType, "put", publicEnv.ContentFile, publicEnv.GCSFileName) Expect(err).ToNot(HaveOccurred()) Expect(session.ExitCode()).ToNot(BeZero()) - Expect(session.Err.Contents()).To(ContainSubstring(client.ErrInvalidROWriteOperation.Error())) + Expect(string(session.Err.Contents())).To(ContainSubstring(client.ErrInvalidROWriteOperation.Error())) }) It("fails to delete", func() { session, err := RunGCSCLI(gcsCLIPath, publicEnv.ConfigPath, storageType, "delete", publicEnv.GCSFileName) Expect(err).ToNot(HaveOccurred()) Expect(session.ExitCode()).ToNot(BeZero()) - Expect(session.Err.Contents()).To(ContainSubstring(client.ErrInvalidROWriteOperation.Error())) + Expect(string(session.Err.Contents())).To(ContainSubstring(client.ErrInvalidROWriteOperation.Error())) + }) + + It("fails to list", func() { + session, err := RunGCSCLI(gcsCLIPath, publicEnv.ConfigPath, storageType, "list", "prefix") + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).ToNot(BeZero()) + Expect(string(session.Err.Contents())).To(ContainSubstring(client.ErrInvalidROWriteOperation.Error())) + }) + + It("fails to get properties", func() { + session, err := RunGCSCLI(gcsCLIPath, publicEnv.ConfigPath, storageType, "properties", publicEnv.GCSFileName) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).ToNot(BeZero()) + Expect(string(session.Err.Contents())).To(ContainSubstring(client.ErrInvalidROWriteOperation.Error())) + }) + + It("fails to delete-recursive", func() { + session, err := RunGCSCLI(gcsCLIPath, publicEnv.ConfigPath, storageType, "delete-recursive", "prefix") + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).ToNot(BeZero()) + Expect(string(session.Err.Contents())).To(ContainSubstring(client.ErrInvalidROWriteOperation.Error())) + }) + + It("fails to copy", func() { + session, err := RunGCSCLI(gcsCLIPath, publicEnv.ConfigPath, storageType, "copy", publicEnv.GCSFileName, "destination-object") + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).ToNot(BeZero()) + Expect(string(session.Err.Contents())).To(ContainSubstring(client.ErrInvalidROWriteOperation.Error())) + }) + + It("fails to create bucket", func() { + session, err := RunGCSCLI(gcsCLIPath, publicEnv.ConfigPath, storageType, "ensure-storage-exists") + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).ToNot(BeZero()) + Expect(string(session.Err.Contents())).To(ContainSubstring(client.ErrInvalidROWriteOperation.Error())) }) }) }) diff --git a/gcs/integration/gcs_static_test.go b/gcs/integration/gcs_static_test.go index c23a008..37dc9e0 100644 --- a/gcs/integration/gcs_static_test.go +++ b/gcs/integration/gcs_static_test.go @@ -69,6 +69,12 @@ var _ = Describe("Integration", func() { Expect(err).ToNot(HaveOccurred()) Expect(resp.StatusCode).To(Equal(200)) defer resp.Body.Close() //nolint:errcheck + + //delete test artifact + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "delete", ctx.GCSFileName) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) + }) Context("encryption key is set", func() { @@ -123,6 +129,11 @@ var _ = Describe("Integration", func() { Expect(err).ToNot(HaveOccurred()) Expect(resp.StatusCode).To(Equal(200)) resp.Body.Close() //nolint:errcheck + + //delete test artifact + session, err = RunGCSCLI(gcsCLIPath, ctx.ConfigPath, storageType, "delete", ctx.GCSFileName) + Expect(err).ToNot(HaveOccurred()) + Expect(session.ExitCode()).To(BeZero()) }) }) }) diff --git a/gcs/integration/utils.go b/gcs/integration/utils.go index 2ba0bfd..5c88153 100644 --- a/gcs/integration/utils.go +++ b/gcs/integration/utils.go @@ -27,6 +27,7 @@ import ( "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" //nolint:staticcheck "github.com/onsi/gomega/gexec" + "golang.org/x/net/context" ) const alphanum = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -69,6 +70,17 @@ func MakeContentFile(content string) string { return tmpFile.Name() } +func deleteBucket(ctx context.Context, bucketName string, configPath string) { + cfgFile, err := os.Open(configPath) + Expect(err).ToNot(HaveOccurred()) + gcsConfig, err := config.NewFromReader(cfgFile) + Expect(err).ToNot(HaveOccurred()) + gcsClient, err := newSDK(ctx, gcsConfig) + Expect(err).ToNot(HaveOccurred()) + err = gcsClient.Bucket(bucketName).Delete(ctx) + Expect(err).ToNot(HaveOccurred()) +} + // RunGCSCLI run the gcscli and outputs the session // after waiting for it to finish func RunGCSCLI(gcsCLIPath, configPath, storageType, subcommand string, args ...string) (*gexec.Session, error) {