From e04b75f3dfd4913443b4db547d8fe5254e25d6d9 Mon Sep 17 00:00:00 2001 From: Tomas Hozza Date: Thu, 11 Mar 2021 22:23:29 +0100 Subject: [PATCH] cloud-cleaner: clean up GCP Storage objects based on metadata Add StorageListObjectsByMetadata() to internal GCP library. The method allows one to search specific Storage bucket for objects based on provided metadata. Extend cloud-cleaner to search for any Storage objects related to the image import, using custom metadata set on the object. Delete all found objects. Signed-off-by: Tomas Hozza --- cmd/cloud-cleaner/main.go | 17 ++++++++++++ internal/cloud/gcp/storage.go | 51 +++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/cmd/cloud-cleaner/main.go b/cmd/cloud-cleaner/main.go index 4f702f869..6fc80e446 100644 --- a/cmd/cloud-cleaner/main.go +++ b/cmd/cloud-cleaner/main.go @@ -28,6 +28,11 @@ func cleanupGCP(testID string, wg *sync.WaitGroup) { } // api.sh test uses '--zone="$GCP_REGION-a"' GCPZone := fmt.Sprintf("%s-a", GCPRegion) + GCPBucket, ok := os.LookupEnv("GCP_BUCKET") + if !ok { + log.Println("[GCP] Error: 'GCP_BUCKET' is not set in the environment.") + return + } // max 62 characters // Must be a match of regex '[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?|[1-9][0-9]{0,19}' // use sha224sum to get predictable testID without invalid characters @@ -73,6 +78,18 @@ func cleanupGCP(testID string, wg *sync.WaitGroup) { log.Printf("[GCP] 🧹 Deleted image import job file %s", cacheObject) } + // Try to find the potentially uploaded Storage objects using custom metadata + objects, err := g.StorageListObjectsByMetadata(GCPBucket, map[string]string{gcp.MetadataKeyImageName: GCPImage}) + if err != nil { + log.Printf("[GCP] Error: %v", err) + } + for _, obj := range objects { + if err = g.StorageObjectDelete(obj.Bucket, obj.Name); err != nil { + log.Printf("[GCP] Error: %v", err) + } + log.Printf("[GCP] 🧹 Deleted object %s/%s related to build of image %s", obj.Bucket, obj.Name, GCPImage) + } + // Try to delete the imported image log.Printf("[GCP] 🧹 Deleting image %s. This should fail if the test succedded.", GCPImage) err = g.ComputeImageDelete(GCPImage) diff --git a/internal/cloud/gcp/storage.go b/internal/cloud/gcp/storage.go index b75247b6b..b5aa63bb3 100644 --- a/internal/cloud/gcp/storage.go +++ b/internal/cloud/gcp/storage.go @@ -196,3 +196,54 @@ func (g *GCP) StorageImageImportCleanup(imageName string) ([]string, []error) { return deletedObjects, errors } + +// StorageListObjectsByMetadata searches specified Storage bucket for objects matching the provided +// metadata. The provided metadata is used for filtering the bucket's content. Therefore if the provided +// metadata is nil, then all objects present in the bucket will be returned. +// +// Matched objects are returned as a list of ObjectAttrs. +// +// Uses: +// - Storage API +func (g *GCP) StorageListObjectsByMetadata(bucket string, metadata map[string]string) ([]*storage.ObjectAttrs, error) { + var matchedObjectAttr []*storage.ObjectAttrs + ctx := context.Background() + + storageClient, err := storage.NewClient(ctx, option.WithCredentials(g.creds)) + if err != nil { + return nil, fmt.Errorf("failed to get Storage client: %v", err) + } + defer storageClient.Close() + + objects := storageClient.Bucket(bucket).Objects(ctx, nil) + for { + obj, err := objects.Next() + if err == iterator.Done { + break + } + if err != nil { + return nil, fmt.Errorf("failure while iterating over bucket objects: %v", err) + } + + // check if the object's Metadata match the provided values + metadataMatch := true + for key, value := range metadata { + objMetadataValue, ok := obj.Metadata[key] + if !ok { + metadataMatch = false + break + } + if objMetadataValue != value { + metadataMatch = false + break + } + } + + if metadataMatch { + matchedObjectAttr = append(matchedObjectAttr, obj) + } + + } + + return matchedObjectAttr, nil +}