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 +}