diff --git a/cmd/cloud-cleaner/main.go b/cmd/cloud-cleaner/main.go deleted file mode 100644 index b87245d82..000000000 --- a/cmd/cloud-cleaner/main.go +++ /dev/null @@ -1,176 +0,0 @@ -// +build integration - -package main - -import ( - "context" - "crypto/sha256" - "fmt" - "log" - "os" - "sync" - - "github.com/Azure/go-autorest/autorest/azure/auth" - - "github.com/osbuild/osbuild-composer/internal/boot/azuretest" - "github.com/osbuild/osbuild-composer/internal/cloud/gcp" - "github.com/osbuild/osbuild-composer/internal/test" -) - -func cleanupGCP(testID string, wg *sync.WaitGroup) { - defer wg.Done() - - log.Println("[GCP] Running clean up") - - GCPRegionName, ok := os.LookupEnv("GCP_REGION") - if !ok { - log.Println("[GCP] Error: 'GCP_REGION' is not set in the environment.") - return - } - 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 - testIDhash := fmt.Sprintf("%x", sha256.Sum224([]byte(testID))) - - // Resource names to clean up - GCPInstance := fmt.Sprintf("vm-%s", testIDhash) - GCPImage := fmt.Sprintf("image-%s", testIDhash) - - // It does not matter if there was any error. If the credentials file was - // read successfully then 'creds' should be non-nil, otherwise it will be - // nil. Both values are acceptable for creating a new "GCP" instance. - // If 'creds' is nil, then GCP library will try to authenticate using - // the instance permissions. - creds, err := gcp.GetCredentialsFromEnv() - if err != nil { - log.Printf("[GCP] Error: %v. This may not be an issue.", err) - } - - // If this fails, there is no point in continuing - g, err := gcp.New(creds) - if err != nil { - log.Printf("[GCP] Error: %v", err) - return - } - - ctx := context.Background() - - // Try to delete potentially running instance - // api.sh chooses a random GCP Zone from regions which names start with the - // `GCPRegionName` value. Since we don't know which one it is, iterate over - // all Regions and Zones in them and try to delete the instance. Unless the - // instance has set "VmDnsSetting:ZonalOnly", which we don't do, this is - // safe and the instance name must be unique for the whole GCP project. - GCPRegions, err := g.ComputeRegionsList(ctx, fmt.Sprintf("name:%s* AND status=UP", GCPRegionName)) - if err != nil { - log.Printf("[GCP] Error: Failed to list GCE regions starting with %q: %v", GCPRegionName, err) - } - for _, GCPRegion := range GCPRegions { - GCPZones, err := g.ComputeUpZonesInRegion(ctx, GCPRegion) - if err != nil { - log.Printf("[GCP] Error: Failed to get available Zones for the '%s' Region: %v", GCPRegionName, err) - break - } - for _, GCPZone := range GCPZones { - log.Printf("[GCP] 🧹 Deleting VM instance %s in %s. "+ - "This should fail if the test succeeded.", GCPInstance, GCPZone.GetName()) - err = g.ComputeInstanceDelete(ctx, GCPZone.GetName(), GCPInstance) - if err == nil { - // If an instance with the given name was successfully deleted in one of the Zones, we are done. - break - } else { - log.Printf("[GCP] Error: %v", err) - } - } - } - - // Try to find the potentially uploaded Storage objects using custom metadata - objects, err := g.StorageListObjectsByMetadata(ctx, GCPBucket, map[string]string{gcp.MetadataKeyImageName: GCPImage}) - if err != nil { - log.Printf("[GCP] Error: %v", err) - } - for _, obj := range objects { - if err = g.StorageObjectDelete(ctx, 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 succeeded.", GCPImage) - err = g.ComputeImageDelete(ctx, GCPImage) - if err != nil { - log.Printf("[GCP] Error: %v", err) - } -} - -func cleanupAzure(testID string, wg *sync.WaitGroup) { - defer wg.Done() - - log.Println("[Azure] Running clean up") - - // Load Azure credentials - creds, err := azuretest.GetAzureCredentialsFromEnv() - if err != nil { - log.Printf("[Azure] Error: %v", err) - return - } - if creds == nil { - log.Println("[Azure] Error: empty credentials") - return - } - - // Delete the vhd image - imageName := "image-" + testID + ".vhd" - log.Println("[Azure] Deleting image. This should fail if the test succeeded.") - err = azuretest.DeleteImageFromAzure(creds, imageName) - if err != nil { - log.Printf("[Azure] Error: %v", err) - } - - // Delete all remaining resources (see the full list in the CleanUpBootedVM function) - log.Println("[Azure] Cleaning up booted VM. This should fail if the test succeeded.") - parameters := azuretest.NewDeploymentParameters(creds, imageName, testID, "") - clientCredentialsConfig := auth.NewClientCredentialsConfig(creds.ClientID, creds.ClientSecret, creds.TenantID) - authorizer, err := clientCredentialsConfig.Authorizer() - if err != nil { - log.Printf("[Azure] Error: %v", err) - return - } - - err = azuretest.CleanUpBootedVM(creds, parameters, authorizer, testID) - if err != nil { - log.Printf("[Azure] Error: %v", err) - } -} - -func main() { - log.Println("Running a cloud cleanup") - var wg sync.WaitGroup - - // Currently scheduled cloud-cleaner supports Azure only. - // In case of scheduled cleanup get testID from env and run Azure cleanup. - // If it's empty generate it and cleanup both GCP and Azure. - testID := os.Getenv("TEST_ID") - if testID == "" { - testID, err := test.GenerateCIArtifactName("") - if err != nil { - log.Fatalf("Failed to get testID: %v", err) - } - log.Printf("TEST_ID=%s", testID) - wg.Add(2) - go cleanupAzure(testID, &wg) - go cleanupGCP(testID, &wg) - wg.Wait() - } else { - wg.Add(1) - go cleanupAzure(testID, &wg) - wg.Wait() - } - -} diff --git a/internal/cloud/gcp/compute.go b/internal/cloud/gcp/compute.go index 77aeb0ea8..703090376 100644 --- a/internal/cloud/gcp/compute.go +++ b/internal/cloud/gcp/compute.go @@ -7,7 +7,6 @@ import ( compute "cloud.google.com/go/compute/apiv1" "github.com/osbuild/osbuild-composer/internal/common" - "google.golang.org/api/iterator" "google.golang.org/api/option" computepb "google.golang.org/genproto/googleapis/cloud/compute/v1" ) @@ -267,141 +266,3 @@ func (g *GCP) ComputeExecuteFunctionForImages(ctx context.Context, f func(*compu imagesIterator := imagesClient.List(ctx, req) return f(imagesIterator) } - -// ComputeInstanceDelete deletes a Compute Engine instance with the given name and -// running in the given zone. If the instance existed and was successfully deleted, -// no error is returned. -// -// Uses: -// - Compute Engine API -func (g *GCP) ComputeInstanceDelete(ctx context.Context, zone, instance string) error { - instancesClient, err := compute.NewInstancesRESTClient(ctx, option.WithCredentials(g.creds)) - if err != nil { - return fmt.Errorf("failed to get Compute Engine Instances client: %v", err) - } - defer instancesClient.Close() - - req := &computepb.DeleteInstanceRequest{ - Project: g.GetProjectID(), - Zone: zone, - Instance: instance, - } - _, err = instancesClient.Delete(ctx, req) - - return err -} - -// ComputeInstanceGet fetches a Compute Engine instance information. If fetching the information -// was successful, it is returned to the caller, otherwise is returned with a proper error. -// -// Uses: -// - Compute Engine API -func (g *GCP) ComputeInstanceGet(ctx context.Context, zone, instance string) (*computepb.Instance, error) { - instancesClient, err := compute.NewInstancesRESTClient(ctx, option.WithCredentials(g.creds)) - if err != nil { - return nil, fmt.Errorf("failed to get Compute Engine Instances client: %v", err) - } - defer instancesClient.Close() - - req := &computepb.GetInstanceRequest{ - Project: g.GetProjectID(), - Instance: instance, - Zone: zone, - } - resp, err := instancesClient.Get(ctx, req) - - return resp, err -} - -// ComputeDiskDelete deletes a Compute Engine disk with the given name and -// running in the given zone. If the disk existed and was successfully deleted, -// no error is returned. -// -// Uses: -// - Compute Engine API -func (g *GCP) ComputeDiskDelete(ctx context.Context, zone, disk string) error { - disksClient, err := compute.NewDisksRESTClient(ctx, option.WithCredentials(g.creds)) - if err != nil { - return fmt.Errorf("failed to get Compute Engine Disks client: %v", err) - } - defer disksClient.Close() - - req := &computepb.DeleteDiskRequest{ - Project: g.GetProjectID(), - Disk: disk, - Zone: zone, - } - _, err = disksClient.Delete(ctx, req) - - return err -} - -// ComputeUpZonesInRegion returns list of zones within the given GCE Region, which are "UP". -// -// Uses: -// - Compute Engine API -func (g *GCP) ComputeUpZonesInRegion(ctx context.Context, region *computepb.Region) ([]*computepb.Zone, error) { - var zones []*computepb.Zone - - zonesClient, err := compute.NewZonesRESTClient(ctx, option.WithCredentials(g.creds)) - if err != nil { - return nil, fmt.Errorf("failed to get Compute Engine Zones client: %v", err) - } - defer zonesClient.Close() - - for _, zoneURL := range region.Zones { - // zone URL example - "https://www.googleapis.com/compute/v1/projects//zones/us-central1-a" - zoneNameSs := strings.Split(zoneURL, "/") - zoneName := zoneNameSs[len(zoneNameSs)-1] - - getZoneReq := &computepb.GetZoneRequest{ - Project: g.GetProjectID(), - Zone: zoneName, - } - zone, err := zonesClient.Get(ctx, getZoneReq) - if err != nil { - return nil, fmt.Errorf("failed to get information about Compute Engine zone '%s': %v", zoneName, err) - } - - // Make sure to return only Zones, which can be used - if zone.GetStatus() == computepb.Zone_UP.String() { - zones = append(zones, zone) - } - } - - return zones, nil -} - -// ComputeRegionsList returns list of GCE regions based on the provided filter. -// -// Uses: -// - Compute Engine API -func (g *GCP) ComputeRegionsList(ctx context.Context, filter string) ([]*computepb.Region, error) { - var regions []*computepb.Region - - regionsClient, err := compute.NewRegionsRESTClient(ctx, option.WithCredentials(g.creds)) - if err != nil { - return nil, fmt.Errorf("failed to get GCE regions client: %v", err) - } - defer regionsClient.Close() - - listRegionsReq := &computepb.ListRegionsRequest{ - Project: g.GetProjectID(), - Filter: common.StringToPtr(filter), - } - regionsIter := regionsClient.List(ctx, listRegionsReq) - - for { - resp, err := regionsIter.Next() - if err == iterator.Done { - break - } - if err != nil { - return nil, fmt.Errorf("error while iterating over GCE regions: %v", err) - } - - regions = append(regions, resp) - } - - return regions, nil -} diff --git a/internal/cloud/gcp/gcp.go b/internal/cloud/gcp/gcp.go index 046f6dc92..2c7abc400 100644 --- a/internal/cloud/gcp/gcp.go +++ b/internal/cloud/gcp/gcp.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io/ioutil" - "os" cloudbuild "cloud.google.com/go/cloudbuild/apiv1" compute "cloud.google.com/go/compute/apiv1" @@ -65,29 +64,6 @@ func NewFromFile(path string) (*GCP, error) { return New(gcpCredentials) } -// GetCredentialsFromEnv reads the service account credentials JSON file from -// the path pointed to by the environment variable name stored in -// 'GCPCredentialsEnvName'. If the content of the JSON file was read successfully, -// its content is returned as []byte, otherwise nil is returned with proper error. -func GetCredentialsFromEnv() ([]byte, error) { - credsPath, exists := os.LookupEnv(GCPCredentialsEnvName) - - if !exists { - return nil, fmt.Errorf("'%s' env variable is not set", GCPCredentialsEnvName) - } - if credsPath == "" { - return nil, fmt.Errorf("'%s' env variable is empty", GCPCredentialsEnvName) - } - - var err error - credentials, err := ioutil.ReadFile(credsPath) - if err != nil { - return nil, fmt.Errorf("Error while reading credentials file: %s", err) - } - - return credentials, nil -} - // GetProjectID returns a string with the Project ID of the project, used for // all GCP operations. func (g *GCP) GetProjectID() string { diff --git a/internal/cloud/gcp/storage.go b/internal/cloud/gcp/storage.go index 6e933ae59..97dd6cb6a 100644 --- a/internal/cloud/gcp/storage.go +++ b/internal/cloud/gcp/storage.go @@ -10,7 +10,6 @@ import ( "os" "cloud.google.com/go/storage" - "google.golang.org/api/iterator" "google.golang.org/api/option" ) @@ -98,53 +97,3 @@ func (g *GCP) StorageObjectDelete(ctx context.Context, bucket, object string) er return nil } - -// 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(ctx context.Context, bucket string, metadata map[string]string) ([]*storage.ObjectAttrs, error) { - var matchedObjectAttr []*storage.ObjectAttrs - - 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 -} diff --git a/osbuild-composer.spec b/osbuild-composer.spec index 0e6c5f605..1d1aeb331 100644 --- a/osbuild-composer.spec +++ b/osbuild-composer.spec @@ -129,7 +129,6 @@ go test -c -tags=integration -ldflags="${TEST_LDFLAGS}" -o _bin/osbuild-auth-tes go test -c -tags=integration -ldflags="${TEST_LDFLAGS}" -o _bin/osbuild-koji-tests %{goipath}/cmd/osbuild-koji-tests go test -c -tags=integration -ldflags="${TEST_LDFLAGS}" -o _bin/osbuild-composer-dbjobqueue-tests %{goipath}/cmd/osbuild-composer-dbjobqueue-tests go test -c -tags=integration -ldflags="${TEST_LDFLAGS}" -o _bin/osbuild-composer-manifest-tests %{goipath}/cmd/osbuild-composer-manifest-tests -go build -tags=integration -ldflags="${TEST_LDFLAGS}" -o _bin/cloud-cleaner %{goipath}/cmd/cloud-cleaner go build -tags=integration -ldflags="${TEST_LDFLAGS}" -o _bin/osbuild-mock-openid-provider %{goipath}/cmd/osbuild-mock-openid-provider %endif @@ -195,7 +194,6 @@ install -m 0755 -vp _bin/osbuild-auth-tests %{buildroot}% install -m 0755 -vp _bin/osbuild-koji-tests %{buildroot}%{_libexecdir}/osbuild-composer-test/ install -m 0755 -vp _bin/osbuild-composer-dbjobqueue-tests %{buildroot}%{_libexecdir}/osbuild-composer-test/ install -m 0755 -vp _bin/osbuild-composer-manifest-tests %{buildroot}%{_libexecdir}/osbuild-composer-test/ -install -m 0755 -vp _bin/cloud-cleaner %{buildroot}%{_libexecdir}/osbuild-composer-test/ install -m 0755 -vp _bin/osbuild-mock-openid-provider %{buildroot}%{_libexecdir}/osbuild-composer-test/ install -m 0755 -vp tools/define-compose-url.sh %{buildroot}%{_libexecdir}/osbuild-composer-test/ install -m 0755 -vp tools/provision.sh %{buildroot}%{_libexecdir}/osbuild-composer-test/