internal/testutil/storage_cleaner.go (109 lines of code) (raw):

// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testutil import ( "context" "fmt" "log" "strings" "testing" "time" "cloud.google.com/go/storage" "github.com/google/uuid" "google.golang.org/api/iterator" ) // TestBucket creates a new bucket with the given prefix and registers a cleanup // function to delete the bucket and any objects it contains when the test finishes. // TestBucket returns the bucket name. It fails the test if bucket creation fails. func TestBucket(ctx context.Context, t *testing.T, projectID, prefix string) string { t.Helper() client, err := storage.NewClient(ctx) if err != nil { t.Fatalf("storage.NewClient: %v", err) } t.Cleanup(func() { client.Close() }) return CreateTestBucket(ctx, t, client, projectID, prefix) } // CreateTestBucket creates a new bucket with the given prefix and registers a // cleanup function to delete the bucket and any objects it contains. // It is equivalent to TestBucket but allows Storage Client re-use. func CreateTestBucket(ctx context.Context, t *testing.T, client *storage.Client, projectID, prefix string) string { t.Helper() bucketName := UniqueBucketName(prefix) b := client.Bucket(bucketName) if err := b.Create(ctx, projectID, nil); err != nil { t.Fatalf("Bucket.Create(%q): %v", bucketName, err) } t.Cleanup(func() { if err := DeleteBucketIfExists(ctx, client, bucketName); err != nil { log.Printf("Bucket.Delete(%q): %v", bucketName, err) } }) return bucketName } // DeleteBucketIfExists deletes a bucket and all its objects. func DeleteBucketIfExists(ctx context.Context, client *storage.Client, bucket string) error { b := client.Bucket(bucket) // Check if the bucket does not exist, return nil. if _, err := b.Attrs(ctx); err != nil { return nil } // Delete all of the elements in the already existent bucket, including noncurrent objects. it := b.Objects(ctx, &storage.Query{ // Versions true to output all generations of objects. Versions: true, }) for { attrs, err := it.Next() if err == iterator.Done { break } if err != nil { return fmt.Errorf("Bucket.Objects(%q): %v", bucket, err) } obj := b.Object(attrs.Name) // Objects with a hold must have the hold released if attrs.EventBasedHold || attrs.TemporaryHold { if _, err := obj.Update(ctx, storage.ObjectAttrsToUpdate{ TemporaryHold: false, EventBasedHold: false, }); err != nil { return fmt.Errorf("Bucket(%q).Object(%q).Update: %v", bucket, attrs.Name, err) } } // Objects with a retention policy must must have the policy removed. if attrs.Retention != nil { _, err = obj.OverrideUnlockedRetention(true).Update(ctx, storage.ObjectAttrsToUpdate{ Retention: &storage.ObjectRetention{}, }) if err != nil { return fmt.Errorf("failed to remove retention from object(%q): %v", attrs.Name, err) } } if err := obj.Generation(attrs.Generation).Delete(ctx); err != nil { return fmt.Errorf("Bucket(%q).Object(%q).Delete: %v", bucket, attrs.Name, err) } } // Then delete the bucket itself. if err := b.Delete(ctx); err != nil { return fmt.Errorf("Bucket.Delete(%q): %v", bucket, err) } // Waits for a bucket to no longer exist, as it can take time to propagate // Errors after 10 successful attempts at retrieving the bucket's attrs retries := 10 delay := 10 * time.Second for i := 0; i < retries; i++ { if _, err := b.Attrs(ctx); err != nil { // Deletion successful. return nil } // Deletion not complete. time.Sleep(delay) } return fmt.Errorf("failed to delete bucket %q", bucket) } // UniqueBucketName returns a unique name with the test prefix. func UniqueBucketName(prefix string) string { return strings.Join([]string{prefix, uuid.New().String()}, "-") } // DeleteExpiredBuckets deletes old testing buckets that weren't cleaned previously. func DeleteExpiredBuckets(client *storage.Client, projectID, prefix string, expireAge time.Duration) error { ctx := context.Background() it := client.Buckets(ctx, projectID) it.Prefix = prefix for { bktAttrs, err := it.Next() if err == iterator.Done { break } if err != nil { return err } if time.Since(bktAttrs.Created) > expireAge { log.Printf("deleting bucket %q, which is more than %s old", bktAttrs.Name, expireAge) if err := DeleteBucketIfExists(ctx, client, bktAttrs.Name); err != nil { return err } } } return nil }