Repository: osbuild/osbuild-composer
Pull request #2081 (2021-11-30 18:45:09)

Cloud API: Support more image types as S3 objects
## More blobby image types in the cloud

This PR adds support for the following image types to the cloud API:
- qcow2
- vmdk
- image-installer

All three are uploaded to the configured s3 bucket and a presigned URL is provided to the user for download (blobby image types).

## Tests

The test script api.sh has been changed to take an image type as argument instead of a target (cloud service).  All supported image types are built, except the `edge-installer`.  Only some blobby image types are verified.

### qcow2 and vmdk

The files are downloaded and run through the `image-info` tool.  The image info is checked to verify that the users specified in the request were created and the package (postgresql) is installed.

### edge-commit

Same as before this PR: The tarball is downloaded and extracted and the commit ID is verified.

## Test runners

I removed the Fedora and CentOS 8 runners for the API test.  I don't know if it's worth testing on those platforms.  I think it's a bit redundant since the API doesn't really do anything different between distros and the actual image builds for each distro are tested elsewhere.  The important part to test with respect to the API I think is the propagation of compose request options down to the manifest generation and the retrieval of accurate info from the API like build status and metadata.

I'm keeping the RHEL 8.4 variant because that does have different distro behaviour caused by the change in osbuild result formats.  It's probably also redundant to test both on 8.6 and 9.0, but I kept them for now.

------

This pull request includes:

- [x] adequate testing for the new functionality or fixed issue
- [ ] adequate documentation informing people about the change such as
  - [ ] submit a PR for the [guides](https://github.com/osbuild/guides) repository if this PR changed any behavior described there: https://www.osbuild.org/guides/

<!--
Thanks for proposing a change to osbuild-composer!

Please don't remove the above check list. These are things that each pull
request must have before it is merged. It helps maintainers to not forget
anything.

If the reason for ticking any of the boxes is ambiguous, please add a short
note explaining why.

In addition, if this pull request fixes a downstream issue, please refer to
test/README.md and add these additional items:

- [ ] 1st commit of any `rhbz#` related PR contains bug reproducer; CI reports FAIL or
- [ ] PR contains automated tests for new functionality and
- [ ] QE has approved reproducer/new tests and
- [ ] Subsequent commits provide bug fixes without modifying the reproducer; CI reports PASS and
- [ ] QE approves this PR; RHBZ status is set to `MODIFIED + Verified=Tested`
-->


achilleas-k: Thanks for the reviews.
I changed the names based on Tom's suggestions (in code and the gitlab-ci config) and cleaned up the embarrassing leftover `print`s.
ondrejbudai: I'm not sure what to do about the test matrix, I don't see much value in testing `edge-*`, `guest-image` and `image-installer` image types again. I personally think that testing of uploading to S3 just once is fine.
Gundersanne: Still needs a rebase because of the 8.5 runners :( sorry
achilleas-k: > I'm not sure what to do about the test matrix, I don't see much value in testing `edge-*`, `guest-image` and `image-installer` image types again. I personally think that testing of uploading to S3 just once is fine.

I think I agree.  There's a couple of things I feel should be covered by tests when it comes to the API:
1. **Options/customisations are properly propagated to the manifest for creating the image.**  This isn't covered by, for example, the weldr build tests because the cloud API options follow different code paths.  However, one image type should be enough to show that options are getting through.  Ideally, we could run a test that makes a request, doesn't actually build the artifact or do the upload, and instead produces a manifest that can be directly inspected for customisataions.
2. **All image types that should be supported by the API are enabled and work.**  This is covered by unit tests.  I think.

Building the `edge-commit` is good for checking that we get the right commit ID back from the metadata.  But it's not easy to check if it gets the right user options.

We could keep it as is for now for a while and then I can convert to the manifest checking test.  Perhaps a whole separate script that runs on all image types but doesn't build them.  Or I can slim it down now.
achilleas-k: Rebased and resolved conflicts.
Removed the new image types from the test matrix as discussed.
I kept the support for the image types in the test script in case we want to adapt them in the future.
gicmo: fwiw, lgtm

Patch:
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9c53fba6c..37f9c65e1 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -313,11 +313,11 @@ Integration:
         INTERNAL_NETWORK: ["true"]
 
 .API_TESTS: &API_TESTS
-  TARGET:
-    - azure
+  IMAGE_TYPE:
     - aws
+    - azure
+    - edge-commit
     - gcp
-    - aws.s3
 
 API:
   stage: test
@@ -328,22 +328,9 @@ API:
     - if: '$CI_PIPELINE_SOURCE == "schedule" && $RUNNER =~ /[\S]+rhel-8.6-[^ga][\S]+/ && $NIGHTLY == "true" && $RHEL_MAJOR == "8"'
   script:
     - schutzbot/deploy.sh
-    - /usr/libexec/tests/osbuild-composer/api.sh ${TARGET}
+    - /usr/libexec/tests/osbuild-composer/api.sh ${IMAGE_TYPE}
   parallel:
     matrix:
-      - <<: *API_TESTS
-        RUNNER:
-          - aws/fedora-33-x86_64
-          # See COMPOSER-919
-          # - aws/fedora-34-x86_64
-          # See COMPOSER-1118
-          # - aws/centos-stream-8-x86_64
-      - TARGET:
-          - aws
-          - gcp
-          - aws.s3
-        RUNNER:
-          - aws/centos-stream-8-x86_64
       - <<: *API_TESTS
         RUNNER:
           - aws/rhel-8.5-ga-x86_64
diff --git a/Makefile b/Makefile
index 6c8d23bf2..abcc30c0a 100644
--- a/Makefile
+++ b/Makefile
@@ -116,6 +116,7 @@ build:
 	go build -o bin/osbuild-upload-aws ./cmd/osbuild-upload-aws/
 	go build -o bin/osbuild-upload-gcp ./cmd/osbuild-upload-gcp/
 	go build -o bin/osbuild-mock-openid-provider ./cmd/osbuild-mock-openid-provider
+	go build -o bin/osbuild-service-maintenance ./cmd/osbuild-service-maintenance
 	go test -c -tags=integration -o bin/osbuild-composer-cli-tests ./cmd/osbuild-composer-cli-tests/main_test.go
 	go test -c -tags=integration -o bin/osbuild-weldr-tests ./internal/client/
 	go test -c -tags=integration -o bin/osbuild-dnf-json-tests ./cmd/osbuild-dnf-json-tests/main_test.go
diff --git a/cmd/osbuild-service-maintenance/aws.go b/cmd/osbuild-service-maintenance/aws.go
new file mode 100644
index 000000000..7c0f4bfc3
--- /dev/null
+++ b/cmd/osbuild-service-maintenance/aws.go
@@ -0,0 +1,72 @@
+package main
+
+import (
+	"context"
+	"sync"
+	"time"
+
+	"github.com/sirupsen/logrus"
+	"golang.org/x/sync/semaphore"
+
+	"github.com/osbuild/osbuild-composer/internal/cloud/awscloud"
+)
+
+func AWSCleanup(maxConcurrentRequests int, dryRun bool, accessKeyID, accessKey, region string, cutoff time.Time) error {
+	a, err := awscloud.New(region, accessKeyID, accessKey, "")
+	if err != nil {
+		return err
+	}
+
+	var wg sync.WaitGroup
+	sem := semaphore.NewWeighted(int64(maxConcurrentRequests))
+	images, err := a.DescribeImagesByTag("Name", "composer-api-*")
+	if err != nil {
+		return err
+	}
+
+	for index, image := range images {
+		// TODO are these actual concerns?
+		if image.ImageId == nil {
+			logrus.Infof("ImageId is nil %v", image)
+			continue
+		}
+		if image.CreationDate == nil {
+			logrus.Infof("Image %v has nil creationdate", *image.ImageId)
+			continue
+		}
+
+		created, err := time.Parse(time.RFC3339, *image.CreationDate)
+		if err != nil {
+			logrus.Infof("Unable to parse date %s for image %s", *image.CreationDate, *image.ImageId)
+			continue
+		}
+
+		if !created.Before(cutoff) {
+			continue
+		}
+
+		if dryRun {
+			logrus.Infof("Dry run, aws image %s in region %s, with creation date %s would be removed", *image.ImageId, region, *image.CreationDate)
+			continue
+		}
+
+		if err = sem.Acquire(context.Background(), 1); err != nil {
+			logrus.Errorf("Error acquiring semaphore: %v", err)
+			continue
+		}
+		wg.Add(1)
+
+		go func(i int) {
+			defer sem.Release(1)
+			defer wg.Done()
+
+			err := a.RemoveSnapshotAndDeregisterImage(images[i])
+			if err != nil {
+				logrus.Errorf("Cleanup for image %s in region %s failed", *images[i].ImageId, region)
+			}
+		}(index)
+	}
+
+	wg.Wait()
+	return nil
+}
diff --git a/cmd/osbuild-service-maintenance/config.go b/cmd/osbuild-service-maintenance/config.go
new file mode 100644
index 000000000..668f17979
--- /dev/null
+++ b/cmd/osbuild-service-maintenance/config.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"reflect"
+)
+
+// Do not write this config to logs or stdout, it contains secrets!
+type Config struct {
+	DryRun                 string `env:"DRY_RUN"`
+	MaxConcurrentRequests  string `env:"MAX_CONCURRENT_REQUESTS"`
+	PGHost                 string `env:"PGHOST"`
+	PGPort                 string `env:"PGPORT"`
+	PGDatabase             string `env:"PGDATABASE"`
+	PGUser                 string `env:"PGUSER"`
+	PGPassword             string `env:"PGPASSWORD"`
+	PGSSLMode              string `env:"PGSSLMODE"`
+	GoogleApplicationCreds string `env:"GOOGLE_APPLICATION_CREDENTIALS"`
+	AWSAccessKeyID         string `env:"AWS_ACCESS_KEY_ID"`
+	AWSSecretAccessKey     string `env:"AWS_SECRET_ACCESS_KEY"`
+}
+
+// *string means the value is not required
+// string means the value is required and should have a default value
+func LoadConfigFromEnv(intf interface{}) error {
+	t := reflect.TypeOf(intf).Elem()
+	v := reflect.ValueOf(intf).Elem()
+
+	for i := 0; i < v.NumField(); i++ {
+		fieldT := t.Field(i)
+		fieldV := v.Field(i)
+		key, ok := fieldT.Tag.Lookup("env")
+		if !ok {
+			return fmt.Errorf("No env tag in config field")
+		}
+
+		confV, ok := os.LookupEnv(key)
+		kind := fieldV.Kind()
+		if ok {
+			switch kind {
+			case reflect.Ptr:
+				if fieldT.Type.Elem().Kind() != reflect.String {
+					return fmt.Errorf("Unsupported type")
+				}
+				fieldV.Set(reflect.ValueOf(&confV))
+			case reflect.String:
+				fieldV.SetString(confV)
+			default:
+				return fmt.Errorf("Unsupported type")
+			}
+		}
+	}
+	return nil
+}
diff --git a/cmd/osbuild-service-maintenance/gcp.go b/cmd/osbuild-service-maintenance/gcp.go
new file mode 100644
index 000000000..f658da83d
--- /dev/null
+++ b/cmd/osbuild-service-maintenance/gcp.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/sirupsen/logrus"
+	"golang.org/x/sync/semaphore"
+	"google.golang.org/api/compute/v1"
+
+	"github.com/osbuild/osbuild-composer/internal/cloud/gcp"
+)
+
+func GCPCleanup(maxConcurrentRequests int, dryRun bool, cutoff time.Time) error {
+	g, err := gcp.New(nil)
+	if err != nil {
+		return err
+	}
+
+	sem := semaphore.NewWeighted(int64(maxConcurrentRequests))
+	var wg sync.WaitGroup
+	removeImageOlderThan := func(images *compute.ImageList) error {
+		for _, image := range images.Items {
+			created, err := time.Parse(time.RFC3339, image.CreationTimestamp)
+			if err != nil {
+				logrus.Errorf("Unable to parse image %s(%d)'s creation timestamp: %v", image.Name, image.Id, err)
+				continue
+			}
+
+			if !created.Before(cutoff) {
+				continue
+			}
+
+			if dryRun {
+				logrus.Infof("Dry run, gcp image %s(%d), with creation date %v would be removed", image.Name, image.Id, created)
+				continue
+			}
+
+			if err = sem.Acquire(context.Background(), 1); err != nil {
+				logrus.Errorf("Error acquiring semaphore: %v", err)
+				continue
+			}
+			wg.Add(1)
+
+			go func(id string) {
+				defer sem.Release(1)
+				defer wg.Done()
+
+				err = g.ComputeImageDelete(context.Background(), id)
+				if err != nil {
+					logrus.Errorf("Error deleting image %s created at %v", id, created)
+				}
+			}(fmt.Sprintf("%d", image.Id))
+		}
+		return nil
+	}
+
+	err = g.ComputeExecuteFunctionForImages(context.Background(), removeImageOlderThan)
+	if err != nil {
+		return err
+	}
+	wg.Wait()
+	return nil
+}
diff --git a/cmd/osbuild-service-maintenance/main.go b/cmd/osbuild-service-maintenance/main.go
new file mode 100644
index 000000000..0cfb2f3be
--- /dev/null
+++ b/cmd/osbuild-service-maintenance/main.go
@@ -0,0 +1,108 @@
+package main
+
+import (
+	"fmt"
+	"strconv"
+	"sync"
+	"time"
+
+	"github.com/sirupsen/logrus"
+
+	"github.com/osbuild/osbuild-composer/internal/jobqueue/dbjobqueue"
+)
+
+func main() {
+	logrus.SetReportCaller(true)
+
+	archs := []string{"x86_64"}
+	jobType := "osbuild"
+	// 14 days
+	cutoff := time.Now().Add(-(time.Hour * 24 * 14))
+	logrus.Infof("Cutoff date: %v", cutoff)
+
+	var conf Config
+	err := LoadConfigFromEnv(&conf)
+	if err != nil {
+		panic(err)
+	}
+	maxCReqs, err := strconv.Atoi(conf.MaxConcurrentRequests)
+	if err != nil {
+		panic(err)
+	}
+	dryRun, err := strconv.ParseBool(conf.DryRun)
+	if err != nil {
+		panic(err)
+	}
+
+	if dryRun {
+		logrus.Info("Dry run, no state will be changed")
+	}
+
+	dbURL := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s",
+		conf.PGUser,
+		conf.PGPassword,
+		conf.PGHost,
+		conf.PGPort,
+		conf.PGDatabase,
+		conf.PGSSLMode,
+	)
+	jobs, err := dbjobqueue.New(dbURL)
+	if err != nil {
+		panic(err)
+	}
+
+	var wg sync.WaitGroup
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		logrus.Info("Cleaning up AWS")
+		err := AWSCleanup(maxCReqs, dryRun, conf.AWSAccessKeyID, conf.AWSSecretAccessKey, "us-east-1", cutoff)
+		if err != nil {
+			logrus.Errorf("AWS cleanup failed: %v", err)
+		}
+	}()
+
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		logrus.Info("Cleaning up GCP")
+		if conf.GoogleApplicationCreds == "" {
+			logrus.Error("GCP credentials not specified")
+			return
+		}
+		err = GCPCleanup(maxCReqs, dryRun, cutoff)
+		if err != nil {
+			logrus.Errorf("GCP Cleanup failed: %v", err)
+		}
+	}()
+
+	wg.Wait()
+	logrus.Info("🦀🦀🦀 cloud cleanup done 🦀🦀🦀")
+
+	var jobTypes []string
+	for _, a := range archs {
+		jobTypes = append(jobTypes, fmt.Sprintf("%s:%s", jobType, a))
+	}
+
+	jobsByType, err := jobs.JobsUptoByType(jobTypes, cutoff)
+	if err != nil {
+		logrus.Errorf("Error querying jobs: %v", err)
+		return
+	}
+
+	for k, v := range jobsByType {
+		logrus.Infof("Deleting jobs and their dependencies of type %v", k)
+		if dryRun {
+			logrus.Infof("Dry run, skipping deletion of jobs: %v", v)
+			continue
+		}
+
+		for _, jobId := range v {
+			err = jobs.DeleteJobIncludingDependencies(jobId)
+			if err != nil {
+				logrus.Errorf("Error deleting job: %v", jobId)
+			}
+		}
+	}
+	logrus.Info("🦀🦀🦀 dbqueue cleanup done 🦀🦀🦀")
+}
diff --git a/cmd/osbuild-upload-aws/main.go b/cmd/osbuild-upload-aws/main.go
index cdb6d2838..9015cdbcd 100644
--- a/cmd/osbuild-upload-aws/main.go
+++ b/cmd/osbuild-upload-aws/main.go
@@ -6,7 +6,7 @@ import (
 
 	"github.com/aws/aws-sdk-go/aws"
 
-	"github.com/osbuild/osbuild-composer/internal/upload/awsupload"
+	"github.com/osbuild/osbuild-composer/internal/cloud/awscloud"
 )
 
 func main() {
@@ -32,7 +32,7 @@ func main() {
 	flag.StringVar(&arch, "arch", "", "arch (x86_64 or aarch64)")
 	flag.Parse()
 
-	a, err := awsupload.New(region, accessKeyID, secretAccessKey, sessionToken)
+	a, err := awscloud.New(region, accessKeyID, secretAccessKey, sessionToken)
 	if err != nil {
 		println(err.Error())
 		return
diff --git a/cmd/osbuild-worker/jobimpl-osbuild.go b/cmd/osbuild-worker/jobimpl-osbuild.go
index 926f7912e..1a89e0c2b 100644
--- a/cmd/osbuild-worker/jobimpl-osbuild.go
+++ b/cmd/osbuild-worker/jobimpl-osbuild.go
@@ -11,11 +11,11 @@ import (
 
 	"github.com/google/uuid"
 
+	"github.com/osbuild/osbuild-composer/internal/cloud/awscloud"
 	"github.com/osbuild/osbuild-composer/internal/cloud/gcp"
 	"github.com/osbuild/osbuild-composer/internal/common"
 	osbuild "github.com/osbuild/osbuild-composer/internal/osbuild2"
 	"github.com/osbuild/osbuild-composer/internal/target"
-	"github.com/osbuild/osbuild-composer/internal/upload/awsupload"
 	"github.com/osbuild/osbuild-composer/internal/upload/azure"
 	"github.com/osbuild/osbuild-composer/internal/upload/koji"
 	"github.com/osbuild/osbuild-composer/internal/upload/vmware"
@@ -37,14 +37,14 @@ func appendTargetError(res *worker.OSBuildJobResult, err error) {
 	res.TargetErrors = append(res.TargetErrors, errStr)
 }
 
-// Returns an *awsupload.AWS object with the credentials of the request. If they
+// Returns an *awscloud.AWS object with the credentials of the request. If they
 // are not accessible, then try to use the one obtained in the worker
 // configuration.
-func (impl *OSBuildJobImpl) getAWS(region string, accessId string, secret string, token string) (*awsupload.AWS, error) {
+func (impl *OSBuildJobImpl) getAWS(region string, accessId string, secret string, token string) (*awscloud.AWS, error) {
 	if accessId != "" && secret != "" {
-		return awsupload.New(region, accessId, secret, token)
+		return awscloud.New(region, accessId, secret, token)
 	} else {
-		return awsupload.NewFromFile(impl.AWSCreds, region)
+		return awscloud.NewFromFile(impl.AWSCreds, region)
 	}
 }
 
diff --git a/distribution/Dockerfile-ubi-maintenance b/distribution/Dockerfile-ubi-maintenance
new file mode 100644
index 000000000..b1c054a5a
--- /dev/null
+++ b/distribution/Dockerfile-ubi-maintenance
@@ -0,0 +1,11 @@
+FROM registry.access.redhat.com/ubi8/go-toolset:latest AS builder
+COPY . .
+ENV GOFLAGS=-mod=vendor
+RUN go install ./cmd/osbuild-service-maintenance/
+
+FROM registry.access.redhat.com/ubi8/ubi-minimal:latest
+RUN mkdir -p "/usr/libexec/osbuild-composer"
+RUN mkdir -p "/etc/osbuild-composer/"
+COPY --from=builder /opt/app-root/src/go/bin/osbuild-service-maintenance /usr/libexec/osbuild-composer/osbuild-service-maintenance
+ENTRYPOINT ["/usr/libexec/osbuild-composer/osbuild-service-maintenance"]
+
diff --git a/go.mod b/go.mod
index 85a41befb..7522517e6 100644
--- a/go.mod
+++ b/go.mod
@@ -47,6 +47,7 @@ require (
 	github.com/vmware/govmomi v0.26.1
 	golang.org/x/mod v0.5.0 // indirect
 	golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1
+	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/sys v0.0.0-20210917161153-d61c044b1678
 	google.golang.org/api v0.58.0
 	google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0
diff --git a/go.sum b/go.sum
index 884f98a42..82865f64c 100644
--- a/go.sum
+++ b/go.sum
@@ -813,6 +813,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/internal/boot/aws.go b/internal/boot/aws.go
index 9840d2efb..063e8e7d7 100644
--- a/internal/boot/aws.go
+++ b/internal/boot/aws.go
@@ -14,8 +14,8 @@ import (
 	"github.com/aws/aws-sdk-go/aws/session"
 	"github.com/aws/aws-sdk-go/service/ec2"
 
+	"github.com/osbuild/osbuild-composer/internal/cloud/awscloud"
 	"github.com/osbuild/osbuild-composer/internal/common"
-	"github.com/osbuild/osbuild-composer/internal/upload/awsupload"
 )
 
 type awsCredentials struct {
@@ -91,7 +91,7 @@ func wrapErrorf(innerError error, format string, a ...interface{}) error {
 // The s3 key is never returned - the same thing is done in osbuild-composer,
 // the user has no way of getting the s3 key.
 func UploadImageToAWS(c *awsCredentials, imagePath string, imageName string) error {
-	uploader, err := awsupload.New(c.Region, c.AccessKeyId, c.SecretAccessKey, c.sessionToken)
+	uploader, err := awscloud.New(c.Region, c.AccessKeyId, c.SecretAccessKey, c.sessionToken)
 	if err != nil {
 		return fmt.Errorf("cannot create aws uploader: %v", err)
 	}
diff --git a/internal/upload/awsupload/awsupload.go b/internal/cloud/awscloud/awscloud.go
similarity index 88%
rename from internal/upload/awsupload/awsupload.go
rename to internal/cloud/awscloud/awscloud.go
index 80739b919..9aa282c1e 100644
--- a/internal/upload/awsupload/awsupload.go
+++ b/internal/cloud/awscloud/awscloud.go
@@ -1,4 +1,4 @@
-package awsupload
+package awscloud
 
 import (
 	"fmt"
@@ -301,6 +301,56 @@ func (a *AWS) Register(name, bucket, key string, shareWith []string, rpmArch str
 	return registerOutput.ImageId, nil
 }
 
+func (a *AWS) RemoveSnapshotAndDeregisterImage(image *ec2.Image) error {
+	if image == nil {
+		return fmt.Errorf("image is nil")
+	}
+
+	var snapshots []*string
+	for _, bdm := range image.BlockDeviceMappings {
+		snapshots = append(snapshots, bdm.Ebs.SnapshotId)
+	}
+
+	_, err := a.ec2.DeregisterImage(
+		&ec2.DeregisterImageInput{
+			ImageId: image.ImageId,
+		},
+	)
+	if err != nil {
+		return err
+	}
+
+	for _, s := range snapshots {
+		_, err = a.ec2.DeleteSnapshot(
+			&ec2.DeleteSnapshotInput{
+				SnapshotId: s,
+			},
+		)
+		if err != nil {
+			// TODO return err?
+			log.Println("Unable to remove snapshot", s)
+		}
+	}
+	return err
+}
+
+// For service maintenance images are discovered by the "Name:composer-api-*" tag filter. Currently
+// all image names in the service are generated, so they're guaranteed to be unique as well. If
+// users are ever allowed to name their images, an extra tag should be added.
+func (a *AWS) DescribeImagesByTag(tagKey, tagValue string) ([]*ec2.Image, error) {
+	imgs, err := a.ec2.DescribeImages(
+		&ec2.DescribeImagesInput{
+			Filters: []*ec2.Filter{
+				{
+					Name:   aws.String(fmt.Sprintf("tag:%s", tagKey)),
+					Values: []*string{aws.String(tagValue)},
+				},
+			},
+		},
+	)
+	return imgs.Images, err
+}
+
 func (a *AWS) S3ObjectPresignedURL(bucket, objectKey string) (string, error) {
 	log.Printf("[AWS] 📋 Generating Presigned URL for S3 object %s/%s", bucket, objectKey)
 	req, _ := a.s3.GetObjectRequest(&s3.GetObjectInput{
diff --git a/internal/cloud/gcp/compute.go b/internal/cloud/gcp/compute.go
index c21e7dc68..380e2ae9c 100644
--- a/internal/cloud/gcp/compute.go
+++ b/internal/cloud/gcp/compute.go
@@ -265,17 +265,31 @@ func (g *GCP) ComputeImageShare(ctx context.Context, imageName string, shareWith
 //
 // Uses:
 //	- Compute Engine API
-func (g *GCP) ComputeImageDelete(ctx context.Context, image string) error {
+func (g *GCP) ComputeImageDelete(ctx context.Context, resourceId string) error {
 	computeService, err := compute.NewService(ctx, option.WithCredentials(g.creds))
 	if err != nil {
 		return fmt.Errorf("failed to get Compute Engine client: %v", err)
 	}
 
-	_, err = computeService.Images.Delete(g.creds.ProjectID, image).Context(ctx).Do()
+	_, err = computeService.Images.Delete(g.creds.ProjectID, resourceId).Context(ctx).Do()
 
 	return err
 }
 
+// ComputeExecuteFunctionForImages will pass all the compute images in the account to a function,
+// which is able to iterate over the images. Useful if something needs to be execute for each image.
+// Uses:
+//	- Compute Engine API
+func (g *GCP) ComputeExecuteFunctionForImages(ctx context.Context, f func(*compute.ImageList) error) error {
+	computeService, err := compute.NewService(ctx, option.WithCredentials(g.creds))
+	if err != nil {
+		return fmt.Errorf("failed to get Compute Engine client: %v", err)
+	}
+
+	imagesService := compute.NewImagesService(computeService)
+	return imagesService.List(g.creds.ProjectID).Pages(ctx, f)
+}
+
 // 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.
diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go
index e392401e8..ebdb8dbc5 100644
--- a/internal/cloudapi/v2/openapi.v2.gen.go
+++ b/internal/cloudapi/v2/openapi.v2.gen.go
@@ -201,12 +201,15 @@ type ImageTypes string
 
 // List of ImageTypes
 const (
-	ImageTypes_aws            ImageTypes = "aws"
-	ImageTypes_azure          ImageTypes = "azure"
-	ImageTypes_edge_commit    ImageTypes = "edge-commit"
-	ImageTypes_edge_container ImageTypes = "edge-container"
-	ImageTypes_edge_installer ImageTypes = "edge-installer"
-	ImageTypes_gcp            ImageTypes = "gcp"
+	ImageTypes_aws             ImageTypes = "aws"
+	ImageTypes_azure           ImageTypes = "azure"
+	ImageTypes_edge_commit     ImageTypes = "edge-commit"
+	ImageTypes_edge_container  ImageTypes = "edge-container"
+	ImageTypes_edge_installer  ImageTypes = "edge-installer"
+	ImageTypes_gcp             ImageTypes = "gcp"
+	ImageTypes_guest_image     ImageTypes = "guest-image"
+	ImageTypes_image_installer ImageTypes = "image-installer"
+	ImageTypes_vsphere         ImageTypes = "vsphere"
 )
 
 // List defines model for List.
@@ -477,64 +480,65 @@ func RegisterHandlers(router EchoRouter, si ServerInterface) {
 var swaggerSpec = []string{
 
 	"H4sIAAAAAAAC/+xbe2/buLL/KoTOBbKLK/ntNDFQ7EnTnJ7sbZsiTndxbxMEtDS2uJVIlaTiOIW/+wUf",
-	"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",
+	"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",
+	"AAA=",
 }
 
 // GetSwagger returns the Swagger specification corresponding to the generated code
diff --git a/internal/cloudapi/v2/openapi.v2.yml b/internal/cloudapi/v2/openapi.v2.yml
index 5e16715f4..3df3c8dfa 100644
--- a/internal/cloudapi/v2/openapi.v2.yml
+++ b/internal/cloudapi/v2/openapi.v2.yml
@@ -504,11 +504,14 @@ components:
       type: string
       enum:
         - aws
-        - gcp
         - azure
         - edge-commit
         - edge-container
         - edge-installer
+        - gcp
+        - guest-image
+        - image-installer
+        - vsphere
     Repository:
       type: object
       required:
diff --git a/internal/cloudapi/v2/v2.go b/internal/cloudapi/v2/v2.go
index da252a991..45646d3b1 100644
--- a/internal/cloudapi/v2/v2.go
+++ b/internal/cloudapi/v2/v2.go
@@ -263,6 +263,10 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
 			return HTTPError(ErrorJSONUnMarshallingError)
 		}
 
+		// For service maintenance, images are discovered by the "Name:composer-api-*"
+		// tag filter. Currently all image names in the service are generated, so they're
+		// guaranteed to be unique as well. If users are ever allowed to name their images,
+		// an extra tag should be added.
 		key := fmt.Sprintf("composer-api-%s", uuid.New().String())
 		t := target.NewAWSTarget(&target.AWSTargetOptions{
 			Filename:          imageType.Filename(),
@@ -278,6 +282,12 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
 		}
 
 		irTarget = t
+	case ImageTypes_guest_image:
+		fallthrough
+	case ImageTypes_vsphere:
+		fallthrough
+	case ImageTypes_image_installer:
+		fallthrough
 	case ImageTypes_edge_installer:
 		fallthrough
 	case ImageTypes_edge_container:
@@ -482,6 +492,12 @@ func imageTypeFromApiImageType(it ImageTypes) string {
 		return "vhd"
 	case ImageTypes_azure:
 		return "vhd"
+	case ImageTypes_guest_image:
+		return "qcow2"
+	case ImageTypes_vsphere:
+		return "vmdk"
+	case ImageTypes_image_installer:
+		return "image-installer"
 	case ImageTypes_edge_commit:
 		return "rhel-edge-commit"
 	case ImageTypes_edge_container:
diff --git a/internal/jobqueue/dbjobqueue/dbjobqueue.go b/internal/jobqueue/dbjobqueue/dbjobqueue.go
index 0059d4d4a..f40320d9a 100644
--- a/internal/jobqueue/dbjobqueue/dbjobqueue.go
+++ b/internal/jobqueue/dbjobqueue/dbjobqueue.go
@@ -96,27 +96,50 @@ const (
 	sqlDeleteHeartbeat = `
                 DELETE FROM heartbeats
                 WHERE id = $1`
+
+	// Maintenance queries
+	sqlQueryJobsUptoByType = `
+                SELECT array_agg(id), type
+                FROM jobs
+                WHERE type = ANY($1) AND finished_at < $2
+                GROUP BY type`
+	sqlQueryDepedenciesRecursively = `
+                WITH RECURSIVE dependencies(d) AS (
+                                SELECT dependency_id
+                                FROM job_dependencies
+                                WHERE job_id = $1
+                                UNION ALL
+                                SELECT dependency_id
+                                FROM dependencies, job_dependencies
+                                WHERE job_dependencies.job_id = d  )
+                SELECT * FROM dependencies`
+	sqlDeleteJobDependencies = `
+                DELETE FROM job_dependencies
+                WHERE dependency_id = ANY($1)`
+	sqlDeleteJobs = `
+                DELETE FROM jobs
+                WHERE id = ANY($1)`
 )
 
-type dbJobQueue struct {
+type DBJobQueue struct {
 	pool *pgxpool.Pool
 }
 
-// Create a new dbJobQueue object for `url`.
-func New(url string) (*dbJobQueue, error) {
+// Create a new DBJobQueue object for `url`.
+func New(url string) (*DBJobQueue, error) {
 	pool, err := pgxpool.Connect(context.Background(), url)
 	if err != nil {
 		return nil, fmt.Errorf("error establishing connection: %v", err)
 	}
 
-	return &dbJobQueue{pool}, nil
+	return &DBJobQueue{pool}, nil
 }
 
-func (q *dbJobQueue) Close() {
+func (q *DBJobQueue) Close() {
 	q.pool.Close()
 }
 
-func (q *dbJobQueue) Enqueue(jobType string, args interface{}, dependencies []uuid.UUID) (uuid.UUID, error) {
+func (q *DBJobQueue) Enqueue(jobType string, args interface{}, dependencies []uuid.UUID) (uuid.UUID, error) {
 	conn, err := q.pool.Acquire(context.Background())
 	if err != nil {
 		return uuid.Nil, fmt.Errorf("error connecting to database: %v", err)
@@ -162,7 +185,7 @@ func (q *dbJobQueue) Enqueue(jobType string, args interface{}, dependencies []uu
 	return id, nil
 }
 
-func (q *dbJobQueue) Dequeue(ctx context.Context, jobTypes []string) (uuid.UUID, uuid.UUID, []uuid.UUID, string, json.RawMessage, error) {
+func (q *DBJobQueue) Dequeue(ctx context.Context, jobTypes []string) (uuid.UUID, uuid.UUID, []uuid.UUID, string, json.RawMessage, error) {
 	// Return early if the context is already canceled.
 	if err := ctx.Err(); err != nil {
 		return uuid.Nil, uuid.Nil, nil, "", nil, jobqueue.ErrDequeueTimeout
@@ -221,7 +244,7 @@ func (q *dbJobQueue) Dequeue(ctx context.Context, jobTypes []string) (uuid.UUID,
 
 	return id, token, dependencies, jobType, args, nil
 }
-func (q *dbJobQueue) DequeueByID(ctx context.Context, id uuid.UUID) (uuid.UUID, []uuid.UUID, string, json.RawMessage, error) {
+func (q *DBJobQueue) DequeueByID(ctx context.Context, id uuid.UUID) (uuid.UUID, []uuid.UUID, string, json.RawMessage, error) {
 	// Return early if the context is already canceled.
 	if err := ctx.Err(); err != nil {
 		return uuid.Nil, nil, "", nil, jobqueue.ErrDequeueTimeout
@@ -260,7 +283,7 @@ func (q *dbJobQueue) DequeueByID(ctx context.Context, id uuid.UUID) (uuid.UUID,
 	return token, dependencies, jobType, args, nil
 }
 
-func (q *dbJobQueue) FinishJob(id uuid.UUID, result interface{}) error {
+func (q *DBJobQueue) FinishJob(id uuid.UUID, result interface{}) error {
 	conn, err := q.pool.Acquire(context.Background())
 	if err != nil {
 		return fmt.Errorf("error connecting to database: %v", err)
@@ -327,7 +350,7 @@ func (q *dbJobQueue) FinishJob(id uuid.UUID, result interface{}) error {
 	return nil
 }
 
-func (q *dbJobQueue) CancelJob(id uuid.UUID) error {
+func (q *DBJobQueue) CancelJob(id uuid.UUID) error {
 	conn, err := q.pool.Acquire(context.Background())
 	if err != nil {
 		return fmt.Errorf("error connecting to database: %v", err)
@@ -348,7 +371,7 @@ func (q *dbJobQueue) CancelJob(id uuid.UUID) error {
 	return nil
 }
 
-func (q *dbJobQueue) JobStatus(id uuid.UUID) (result json.RawMessage, queued, started, finished time.Time, canceled bool, deps []uuid.UUID, err error) {
+func (q *DBJobQueue) JobStatus(id uuid.UUID) (result json.RawMessage, queued, started, finished time.Time, canceled bool, deps []uuid.UUID, err error) {
 	conn, err := q.pool.Acquire(context.Background())
 	if err != nil {
 		return
@@ -380,7 +403,7 @@ func (q *dbJobQueue) JobStatus(id uuid.UUID) (result json.RawMessage, queued, st
 }
 
 // Job returns all the parameters that define a job (everything provided during Enqueue).
-func (q *dbJobQueue) Job(id uuid.UUID) (jobType string, args json.RawMessage, dependencies []uuid.UUID, err error) {
+func (q *DBJobQueue) Job(id uuid.UUID) (jobType string, args json.RawMessage, dependencies []uuid.UUID, err error) {
 	conn, err := q.pool.Acquire(context.Background())
 	if err != nil {
 		return
@@ -400,7 +423,7 @@ func (q *dbJobQueue) Job(id uuid.UUID) (jobType string, args json.RawMessage, de
 }
 
 // Find job by token, this will return an error if the job hasn't been dequeued
-func (q *dbJobQueue) IdFromToken(token uuid.UUID) (id uuid.UUID, err error) {
+func (q *DBJobQueue) IdFromToken(token uuid.UUID) (id uuid.UUID, err error) {
 	conn, err := q.pool.Acquire(context.Background())
 	if err != nil {
 		return uuid.Nil, fmt.Errorf("error establishing connection: %v", err)
@@ -418,7 +441,7 @@ func (q *dbJobQueue) IdFromToken(token uuid.UUID) (id uuid.UUID, err error) {
 }
 
 // Get a list of tokens which haven't been updated in the specified time frame
-func (q *dbJobQueue) Heartbeats(olderThan time.Duration) (tokens []uuid.UUID) {
+func (q *DBJobQueue) Heartbeats(olderThan time.Duration) (tokens []uuid.UUID) {
 	conn, err := q.pool.Acquire(context.Background())
 	if err != nil {
 		return
@@ -449,7 +472,7 @@ func (q *dbJobQueue) Heartbeats(olderThan time.Duration) (tokens []uuid.UUID) {
 }
 
 // Reset the last heartbeat time to time.Now()
-func (q *dbJobQueue) RefreshHeartbeat(token uuid.UUID) {
+func (q *DBJobQueue) RefreshHeartbeat(token uuid.UUID) {
 	conn, err := q.pool.Acquire(context.Background())
 	if err != nil {
 		return
@@ -465,7 +488,7 @@ func (q *dbJobQueue) RefreshHeartbeat(token uuid.UUID) {
 	}
 }
 
-func (q *dbJobQueue) jobDependencies(ctx context.Context, conn *pgxpool.Conn, id uuid.UUID) ([]uuid.UUID, error) {
+func (q *DBJobQueue) jobDependencies(ctx context.Context, conn *pgxpool.Conn, id uuid.UUID) ([]uuid.UUID, error) {
 	rows, err := conn.Query(ctx, sqlQueryDependencies, id)
 	if err != nil {
 		return nil, err
@@ -488,3 +511,91 @@ func (q *dbJobQueue) jobDependencies(ctx context.Context, conn *pgxpool.Conn, id
 
 	return dependencies, nil
 }
+
+// return map id -> jobtype ?
+func (q *DBJobQueue) JobsUptoByType(jobTypes []string, upto time.Time) (result map[string][]uuid.UUID, err error) {
+	result = make(map[string][]uuid.UUID)
+
+	conn, err := q.pool.Acquire(context.Background())
+	if err != nil {
+		err = fmt.Errorf("error connecting to database: %v", err)
+		return
+	}
+	defer conn.Release()
+
+	rows, err := conn.Query(context.Background(), sqlQueryJobsUptoByType, jobTypes, upto)
+	if err != nil {
+		return
+	}
+	defer rows.Close()
+
+	for rows.Next() {
+		var ids []uuid.UUID
+		var jt string
+		err = rows.Scan(&ids, &jt)
+		if err != nil {
+			return
+		}
+
+		result[jt] = ids
+	}
+	err = rows.Err()
+	return
+}
+
+// Deletes single job and dependencies (recursively)
+func (q *DBJobQueue) DeleteJobIncludingDependencies(jobId uuid.UUID) error {
+	conn, err := q.pool.Acquire(context.Background())
+	if err != nil {
+
+		return fmt.Errorf("error connecting to database: %v", err)
+	}
+	defer conn.Release()
+
+	tx, err := conn.Begin(context.Background())
+	if err != nil {
+		return fmt.Errorf("error starting database transaction: %v", err)
+	}
+	defer func() {
+		err := tx.Rollback(context.Background())
+		if err != nil && !errors.As(err, &pgx.ErrTxClosed) {
+			logrus.Error("error rolling back enqueue transaction: ", err)
+		}
+	}()
+
+	rows, err := conn.Query(context.Background(), sqlQueryDepedenciesRecursively, jobId)
+	if err != nil {
+		return fmt.Errorf("error querying the job's dependencies: %v", err)
+	}
+
+	var dependencies []uuid.UUID
+	for rows.Next() {
+		var dep uuid.UUID
+		err = rows.Scan(&dep)
+		if err != nil {
+			return err
+		}
+
+		dependencies = append(dependencies, dep)
+	}
+
+	depTag, err := conn.Exec(context.Background(), sqlDeleteJobDependencies, dependencies)
+	if err != nil {
+		return fmt.Errorf("Error removing from dependencies recursively for job %v: %v", jobId, err)
+	}
+
+	jobAndDependencies := append(dependencies, jobId)
+	jobsTag, err := conn.Exec(context.Background(), sqlDeleteJobs, jobAndDependencies)
+	if err != nil {
+		return fmt.Errorf("Error removing from jobs recursively for job %v: %v", jobId, err)
+	}
+
+	err = tx.Commit(context.Background())
+	if err != nil {
+		return fmt.Errorf("unable to commit database transaction: %v", err)
+	}
+
+	logrus.Infof("Removed %d rows from dependencies for job %v", depTag.RowsAffected(), jobId)
+	logrus.Infof("Removed %d rows from jobs for job %v, this includes dependencies", jobsTag.RowsAffected(), jobId)
+	return nil
+}
diff --git a/tools/appsre-build-deploy.sh b/tools/appsre-build-deploy.sh
index a01c83a31..acb206029 100755
--- a/tools/appsre-build-deploy.sh
+++ b/tools/appsre-build-deploy.sh
@@ -16,3 +16,8 @@ docker --config="$DOCKER_CONF" login -u="$QUAY_USER" -p="$QUAY_TOKEN" quay.io
 docker --config="$DOCKER_CONF" build -f distribution/Dockerfile-ubi -t "${IMAGE_NAME}:${IMAGE_TAG}" .
 docker --config="$DOCKER_CONF" push "${IMAGE_NAME}:${IMAGE_TAG}"
 
+# Maintenance image
+IMAGE_NAME="quay.io/app-sre/composer-maintenance"
+IMAGE_TAG=$(git rev-parse --short=7 HEAD)
+docker --config="$DOCKER_CONF" build -f distribution/Dockerfile-ubi-maintenance -t "${IMAGE_NAME}:${IMAGE_TAG}" .
+docker --config="$DOCKER_CONF" push "${IMAGE_NAME}:${IMAGE_TAG}"
diff --git a/vendor/golang.org/x/sync/AUTHORS b/vendor/golang.org/x/sync/AUTHORS
new file mode 100644
index 000000000..15167cd74
--- /dev/null
+++ b/vendor/golang.org/x/sync/AUTHORS
@@ -0,0 +1,3 @@
+# This source code refers to The Go Authors for copyright purposes.
+# The master list of authors is in the main Go distribution,
+# visible at http://tip.golang.org/AUTHORS.
diff --git a/vendor/golang.org/x/sync/CONTRIBUTORS b/vendor/golang.org/x/sync/CONTRIBUTORS
new file mode 100644
index 000000000..1c4577e96
--- /dev/null
+++ b/vendor/golang.org/x/sync/CONTRIBUTORS
@@ -0,0 +1,3 @@
+# This source code was written by the Go contributors.
+# The master list of contributors is in the main Go distribution,
+# visible at http://tip.golang.org/CONTRIBUTORS.
diff --git a/vendor/golang.org/x/sync/LICENSE b/vendor/golang.org/x/sync/LICENSE
new file mode 100644
index 000000000..6a66aea5e
--- /dev/null
+++ b/vendor/golang.org/x/sync/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/golang.org/x/sync/PATENTS b/vendor/golang.org/x/sync/PATENTS
new file mode 100644
index 000000000..733099041
--- /dev/null
+++ b/vendor/golang.org/x/sync/PATENTS
@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Go project.
+
+Google hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section)
+patent license to make, have made, use, offer to sell, sell, import,
+transfer and otherwise run, modify and propagate the contents of this
+implementation of Go, where such license applies only to those patent
+claims, both currently owned or controlled by Google and acquired in
+the future, licensable by Google that are necessarily infringed by this
+implementation of Go.  This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation.  If you or your agent or exclusive licensee institute or
+order or agree to the institution of patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging
+that this implementation of Go or any code incorporated within this
+implementation of Go constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation of Go
+shall terminate as of the date such litigation is filed.
diff --git a/vendor/golang.org/x/sync/semaphore/semaphore.go b/vendor/golang.org/x/sync/semaphore/semaphore.go
new file mode 100644
index 000000000..30f632c57
--- /dev/null
+++ b/vendor/golang.org/x/sync/semaphore/semaphore.go
@@ -0,0 +1,136 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package semaphore provides a weighted semaphore implementation.
+package semaphore // import "golang.org/x/sync/semaphore"
+
+import (
+	"container/list"
+	"context"
+	"sync"
+)
+
+type waiter struct {
+	n     int64
+	ready chan<- struct{} // Closed when semaphore acquired.
+}
+
+// NewWeighted creates a new weighted semaphore with the given
+// maximum combined weight for concurrent access.
+func NewWeighted(n int64) *Weighted {
+	w := &Weighted{size: n}
+	return w
+}
+
+// Weighted provides a way to bound concurrent access to a resource.
+// The callers can request access with a given weight.
+type Weighted struct {
+	size    int64
+	cur     int64
+	mu      sync.Mutex
+	waiters list.List
+}
+
+// Acquire acquires the semaphore with a weight of n, blocking until resources
+// are available or ctx is done. On success, returns nil. On failure, returns
+// ctx.Err() and leaves the semaphore unchanged.
+//
+// If ctx is already done, Acquire may still succeed without blocking.
+func (s *Weighted) Acquire(ctx context.Context, n int64) error {
+	s.mu.Lock()
+	if s.size-s.cur >= n && s.waiters.Len() == 0 {
+		s.cur += n
+		s.mu.Unlock()
+		return nil
+	}
+
+	if n > s.size {
+		// Don't make other Acquire calls block on one that's doomed to fail.
+		s.mu.Unlock()
+		<-ctx.Done()
+		return ctx.Err()
+	}
+
+	ready := make(chan struct{})
+	w := waiter{n: n, ready: ready}
+	elem := s.waiters.PushBack(w)
+	s.mu.Unlock()
+
+	select {
+	case <-ctx.Done():
+		err := ctx.Err()
+		s.mu.Lock()
+		select {
+		case <-ready:
+			// Acquired the semaphore after we were canceled.  Rather than trying to
+			// fix up the queue, just pretend we didn't notice the cancelation.
+			err = nil
+		default:
+			isFront := s.waiters.Front() == elem
+			s.waiters.Remove(elem)
+			// If we're at the front and there're extra tokens left, notify other waiters.
+			if isFront && s.size > s.cur {
+				s.notifyWaiters()
+			}
+		}
+		s.mu.Unlock()
+		return err
+
+	case <-ready:
+		return nil
+	}
+}
+
+// TryAcquire acquires the semaphore with a weight of n without blocking.
+// On success, returns true. On failure, returns false and leaves the semaphore unchanged.
+func (s *Weighted) TryAcquire(n int64) bool {
+	s.mu.Lock()
+	success := s.size-s.cur >= n && s.waiters.Len() == 0
+	if success {
+		s.cur += n
+	}
+	s.mu.Unlock()
+	return success
+}
+
+// Release releases the semaphore with a weight of n.
+func (s *Weighted) Release(n int64) {
+	s.mu.Lock()
+	s.cur -= n
+	if s.cur < 0 {
+		s.mu.Unlock()
+		panic("semaphore: released more than held")
+	}
+	s.notifyWaiters()
+	s.mu.Unlock()
+}
+
+func (s *Weighted) notifyWaiters() {
+	for {
+		next := s.waiters.Front()
+		if next == nil {
+			break // No more waiters blocked.
+		}
+
+		w := next.Value.(waiter)
+		if s.size-s.cur < w.n {
+			// Not enough tokens for the next waiter.  We could keep going (to try to
+			// find a waiter with a smaller request), but under load that could cause
+			// starvation for large requests; instead, we leave all remaining waiters
+			// blocked.
+			//
+			// Consider a semaphore used as a read-write lock, with N tokens, N
+			// readers, and one writer.  Each reader can Acquire(1) to obtain a read
+			// lock.  The writer can Acquire(N) to obtain a write lock, excluding all
+			// of the readers.  If we allow the readers to jump ahead in the queue,
+			// the writer will starve — there is always one token available for every
+			// reader.
+			break
+		}
+
+		s.cur += w.n
+		s.waiters.Remove(next)
+		close(w.ready)
+	}
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 9390f378c..b12a928ce 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -416,6 +416,9 @@ golang.org/x/oauth2/google/internal/externalaccount
 golang.org/x/oauth2/internal
 golang.org/x/oauth2/jws
 golang.org/x/oauth2/jwt
+# golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
+## explicit
+golang.org/x/sync/semaphore
 # golang.org/x/sys v0.0.0-20210917161153-d61c044b1678
 ## explicit
 golang.org/x/sys/internal/unsafeheader


Test patch:
diff --git a/cmd/osbuild-composer-dbjobqueue-tests/main_test.go b/cmd/osbuild-composer-dbjobqueue-tests/main_test.go
index 47be99d68..1134fc3c8 100644
--- a/cmd/osbuild-composer-dbjobqueue-tests/main_test.go
+++ b/cmd/osbuild-composer-dbjobqueue-tests/main_test.go
@@ -6,18 +6,21 @@ import (
 	"context"
 	"fmt"
 	"testing"
+	"time"
 
+	"github.com/google/uuid"
 	"github.com/jackc/pgx/v4"
+	"github.com/stretchr/testify/require"
 
 	"github.com/osbuild/osbuild-composer/internal/jobqueue"
 	"github.com/osbuild/osbuild-composer/internal/jobqueue/dbjobqueue"
 	"github.com/osbuild/osbuild-composer/internal/jobqueue/jobqueuetest"
 )
 
-func TestJobQueueInterface(t *testing.T) {
-	jobqueuetest.TestJobQueue(t, func() (jobqueue.JobQueue, func(), error) {
-		url := "postgres://postgres:foobar@localhost:5432/osbuildcomposer"
+const url = "postgres://postgres:foobar@localhost:5432/osbuildcomposer"
 
+func TestJobQueueInterface(t *testing.T) {
+	makeJobQueue := func() (jobqueue.JobQueue, func(), error) {
 		// clear db before each run
 		conn, err := pgx.Connect(context.Background(), url)
 		if err != nil {
@@ -43,5 +46,176 @@ func TestJobQueueInterface(t *testing.T) {
 			q.Close()
 		}
 		return q, stop, nil
-	})
+	}
+
+	jobqueuetest.TestJobQueue(t, makeJobQueue)
+
+	wrap := func(f func(t *testing.T, q *dbjobqueue.DBJobQueue)) func(*testing.T) {
+		q, stop, err := makeJobQueue()
+		require.NoError(t, err)
+		return func(t *testing.T) {
+			defer stop() // use defer because f() might call testing.T.FailNow()
+			dbq, ok := q.(*dbjobqueue.DBJobQueue)
+			require.True(t, ok)
+			f(t, dbq)
+		}
+	}
+
+	t.Run("maintenance-query-jobs-before", wrap(testJobsUptoByType))
+	t.Run("maintenance-delete-job-and-dependencies", wrap(testDeleteJobAndDependencies))
+}
+
+func setFinishedAt(t *testing.T, q *dbjobqueue.DBJobQueue, id uuid.UUID, finished time.Time) {
+	conn, err := pgx.Connect(context.Background(), url)
+	require.NoError(t, err)
+	defer conn.Close(context.Background())
+
+	started := finished.Add(-time.Second)
+	queued := started.Add(-time.Second)
+
+	_, err = conn.Exec(context.Background(), "UPDATE jobs SET queued_at = $1, started_at = $2, finished_at = $3 WHERE id = $4", queued, started, finished, id)
+	require.NoError(t, err)
+}
+
+func testJobsUptoByType(t *testing.T, q *dbjobqueue.DBJobQueue) {
+	date80 := time.Date(1980, time.January, 1, 0, 0, 0, 0, time.UTC)
+	date85 := time.Date(1985, time.January, 1, 0, 0, 0, 0, time.UTC)
+	date90 := time.Date(1990, time.January, 1, 0, 0, 0, 0, time.UTC)
+
+	id80, err := q.Enqueue("octopus", nil, nil)
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, id80)
+	_,_,_,_,_, err = q.Dequeue(context.Background(), []string{"octopus"})
+	require.NoError(t, err)
+	err = q.FinishJob(id80, nil)
+	require.NoError(t, err)
+	setFinishedAt(t, q, id80, date80)
+
+	id85, err := q.Enqueue("octopus", nil, nil)
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, id85)
+	_,_,_,_,_, err = q.Dequeue(context.Background(), []string{"octopus"})
+	require.NoError(t, err)
+	err = q.FinishJob(id85, nil)
+	require.NoError(t, err)
+	setFinishedAt(t, q, id85, date85)
+
+	ids, err := q.JobsUptoByType([]string{"octopus"}, date85)
+	require.NoError(t, err)
+	require.ElementsMatch(t, []uuid.UUID{id80}, ids["octopus"])
+
+	ids, err = q.JobsUptoByType([]string{"octopus"}, date90)
+	require.NoError(t, err)
+	require.ElementsMatch(t, []uuid.UUID{id80, id85}, ids["octopus"])
 }
+
+func testDeleteJobAndDependencies(t *testing.T, q *dbjobqueue.DBJobQueue) {
+	// id1 -> id2 -> id3
+	id1, err := q.Enqueue("octopus", nil, nil)
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, id1)
+	id2, err := q.Enqueue("octopus", nil, []uuid.UUID{id1})
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, id2)
+	id3, err := q.Enqueue("octopus", nil, []uuid.UUID{id2})
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, id3)
+
+	c1, err := q.Enqueue("octopus", nil, nil)
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, c1)
+	c2, err := q.Enqueue("octopus", nil, []uuid.UUID{c1})
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, c2)
+	c3, err := q.Enqueue("octopus", nil, []uuid.UUID{c2})
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, c3)
+	controls := []uuid.UUID{c1, c2, c3}
+
+	_,_,_, err = q.Job(c1)
+	require.NoError(t, err)
+
+	require.NoError(t, q.DeleteJobIncludingDependencies(id3))
+	for _, id := range []uuid.UUID{id1, id2, id3} {
+		_,_,_, err = q.Job(id)
+		require.ErrorIs(t, err, jobqueue.ErrNotExist)
+	}
+
+	// controls should still exist
+	for _, c := range controls {
+		_,_,_, err = q.Job(c)
+		require.NoError(t, err)
+	}
+
+	// id1 -> id2 -> id4 && id3 -> id4
+	id1, err = q.Enqueue("octopus", nil, nil)
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, id1)
+	id2, err = q.Enqueue("octopus", nil, []uuid.UUID{id1})
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, id2)
+	id3, err = q.Enqueue("octopus", nil, nil)
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, id3)
+	id4, err := q.Enqueue("octopus", nil, []uuid.UUID{id2, id3})
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, id4)
+
+	require.NoError(t, q.DeleteJobIncludingDependencies(id4))
+	for _, id := range []uuid.UUID{id1, id2, id3, id4} {
+		_,_,_, err = q.Job(id)
+		require.ErrorIs(t, err, jobqueue.ErrNotExist)
+	}
+
+	// controls should still exist
+	for _, c := range controls {
+		_,_,_, err = q.Job(c)
+		require.NoError(t, err)
+	}
+
+	// id1 has 2 dependants, and the maintenance queries currently do not account for this
+	// situation as it does not occur in the service.  This should be changed once we allow
+	// multiple build job per depsolve job, and the depsolve job should only be removed once all
+	// the build jobs have been dealt with.
+	id1, err = q.Enqueue("octopus", nil, nil)
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, id1)
+	id2a, err := q.Enqueue("octopus", nil, []uuid.UUID{id1})
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, id2a)
+	id2b, err := q.Enqueue("octopus", nil, []uuid.UUID{id1})
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, id2b)
+	id3, err = q.Enqueue("octopus", nil, []uuid.UUID{id2a})
+	require.NoError(t, err)
+	require.NotEqual(t, uuid.Nil, id3)
+
+	require.NoError(t, q.DeleteJobIncludingDependencies(id3))
+	for _, id := range []uuid.UUID{id1, id2a, id3} {
+		_,_,_, err = q.Job(id)
+		require.ErrorIs(t, err, jobqueue.ErrNotExist)
+	}
+
+	// id2b still exists
+	_,_,_, err = q.Job(id2b)
+	require.NoError(t, err)
+
+	// id2b can still be deleted with it's dependencies missing
+	require.NoError(t, q.DeleteJobIncludingDependencies(id2b))
+	_,_,_, err = q.Job(id2b)
+	require.ErrorIs(t, err, jobqueue.ErrNotExist)
+
+	// controls should still exist
+	for _, c := range controls {
+		_,_,_, err = q.Job(c)
+		require.NoError(t, err)
+	}
+
+	require.NoError(t, q.DeleteJobIncludingDependencies(uuid.Nil))
+	// controls should still exist
+	for _, c := range controls {
+		_,_,_, err = q.Job(c)
+		require.NoError(t, err)
+	}
+}
+
diff --git a/internal/cloudapi/v2/v2_test.go b/internal/cloudapi/v2/v2_test.go
index 7db56aa6b..ebb9a55f8 100644
--- a/internal/cloudapi/v2/v2_test.go
+++ b/internal/cloudapi/v2/v2_test.go
@@ -540,4 +540,61 @@ func TestImageTypes(t *testing.T) {
 		"href": "/api/image-builder-composer/v2/compose",
 		"kind": "ComposeId"
 	}`, "id")
+	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
+	{
+		"distribution": "%s",
+		"image_request":{
+			"architecture": "%s",
+			"image_type": "%s",
+			"repositories": [{
+				"baseurl": "somerepo.org",
+				"rhsm": false
+			}],
+			"upload_options": {
+				"region": "eu-central-1"
+			}
+		 }
+	}`, test_distro.TestDistroName, test_distro.TestArch3Name, string(v2.ImageTypes_image_installer)), http.StatusCreated, `
+	{
+		"href": "/api/image-builder-composer/v2/compose",
+		"kind": "ComposeId"
+	}`, "id")
+	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
+	{
+		"distribution": "%s",
+		"image_request":{
+			"architecture": "%s",
+			"image_type": "%s",
+			"repositories": [{
+				"baseurl": "somerepo.org",
+				"rhsm": false
+			}],
+			"upload_options": {
+				"region": "eu-central-1"
+			}
+		 }
+	}`, test_distro.TestDistroName, test_distro.TestArch3Name, string(v2.ImageTypes_guest_image)), http.StatusCreated, `
+	{
+		"href": "/api/image-builder-composer/v2/compose",
+		"kind": "ComposeId"
+	}`, "id")
+	test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
+	{
+		"distribution": "%s",
+		"image_request":{
+			"architecture": "%s",
+			"image_type": "%s",
+			"repositories": [{
+				"baseurl": "somerepo.org",
+				"rhsm": false
+			}],
+			"upload_options": {
+				"region": "eu-central-1"
+			}
+		 }
+	}`, test_distro.TestDistroName, test_distro.TestArch3Name, string(v2.ImageTypes_vsphere)), http.StatusCreated, `
+	{
+		"href": "/api/image-builder-composer/v2/compose",
+		"kind": "ComposeId"
+	}`, "id")
 }
diff --git a/internal/distro/test_distro/distro.go b/internal/distro/test_distro/distro.go
index 7f5550aa4..585d846f9 100644
--- a/internal/distro/test_distro/distro.go
+++ b/internal/distro/test_distro/distro.go
@@ -47,10 +47,13 @@ const (
 	TestImageType2Name = "test_type2"
 
 	// added for cloudapi tests
-	TestImageTypeAmi           = "ami"
-	TestImageTypeVhd           = "vhd"
-	TestImageTypeEdgeCommit    = "rhel-edge-commit"
-	TestImageTypeEdgeInstaller = "rhel-edge-installer"
+	TestImageTypeAmi            = "ami"
+	TestImageTypeVhd            = "vhd"
+	TestImageTypeEdgeCommit     = "rhel-edge-commit"
+	TestImageTypeEdgeInstaller  = "rhel-edge-installer"
+	TestImageTypeImageInstaller = "image-installer"
+	TestImageTypeQcow2          = "qcow2"
+	TestImageTypeVmdk           = "vmdk"
 )
 
 // TestDistro
@@ -259,9 +262,21 @@ func newTestDistro(name, modulePlatformID, releasever string) *TestDistro {
 		name: TestImageTypeEdgeInstaller,
 	}
 
+	it7 := TestImageType{
+		name: TestImageTypeImageInstaller,
+	}
+
+	it8 := TestImageType{
+		name: TestImageTypeQcow2,
+	}
+
+	it9 := TestImageType{
+		name: TestImageTypeVmdk,
+	}
+
 	ta1.addImageTypes(it1)
 	ta2.addImageTypes(it1, it2)
-	ta3.addImageTypes(it3, it4, it5, it6)
+	ta3.addImageTypes(it3, it4, it5, it6, it7, it8, it9)
 
 	td.addArches(&ta1, &ta2, &ta3)
 
diff --git a/test/cases/api.sh b/test/cases/api.sh
index adb929f5e..07bf99aab 100755
--- a/test/cases/api.sh
+++ b/test/cases/api.sh
@@ -12,8 +12,21 @@
 # from a run on a remote continuous integration system.
 #
 
+if (( $# != 1 )); then
+    echo "$0 requires exactly one argument"
+    echo "Please specify an image type to build"
+    exit 1
+fi
+
 set -euxo pipefail
 
+IMAGE_TYPE="$1"
+
+# Colorful timestamped output.
+function greenprint {
+    echo -e "\033[1;32m[$(date -Isecond)] ${1}\033[0m"
+}
+
 ARTIFACTS=ci-artifacts
 mkdir -p "${ARTIFACTS}"
 
@@ -85,7 +98,7 @@ EOF
 sudo systemctl restart osbuild-composer
 
 #
-# Which cloud provider are we testing?
+# Cloud provider / target names
 #
 
 CLOUD_PROVIDER_AWS="aws"
@@ -93,35 +106,44 @@ CLOUD_PROVIDER_GCP="gcp"
 CLOUD_PROVIDER_AZURE="azure"
 CLOUD_PROVIDER_AWS_S3="aws.s3"
 
-CLOUD_PROVIDER=${1:-$CLOUD_PROVIDER_AWS}
-
-case $CLOUD_PROVIDER in
-  "$CLOUD_PROVIDER_AWS")
-    echo "Testing AWS"
-    ;;
-  "$CLOUD_PROVIDER_GCP")
-    echo "Testing Google Cloud Platform"
-    if [[ $ID == fedora ]]; then
-        echo "Skipped, Fedora isn't supported by GCP"
-        exit 0
-    fi
-    ;;
-  "$CLOUD_PROVIDER_AZURE")
-    echo "Testing Azure"
-    ;;
-  "$CLOUD_PROVIDER_AWS_S3")
-    echo "Testing S3 bucket upload"
-    if [[ $ID != "rhel" ]]; then
-        echo "Skipped. S3 upload test is only tested on RHEL (testing only image type: rhel-edge-commit)."
-        exit 0
-    fi
-    ;;
-  *)
-    echo "Unknown cloud provider '$CLOUD_PROVIDER'. Supported are '$CLOUD_PROVIDER_AWS', '$CLOUD_PROVIDER_AWS_S3', '$CLOUD_PROVIDER_GCP', '$CLOUD_PROVIDER_AZURE'"
-    exit 1
-    ;;
+#
+# Supported Image type names
+#
+IMAGE_TYPE_AWS="aws"
+IMAGE_TYPE_AZURE="azure"
+IMAGE_TYPE_EDGE_COMMIT="edge-commit"
+IMAGE_TYPE_EDGE_CONTAINER="edge-container"
+IMAGE_TYPE_EDGE_INSTALLER="edge-installer"
+IMAGE_TYPE_GCP="gcp"
+IMAGE_TYPE_IMAGE_INSTALLER="image-installer"
+IMAGE_TYPE_GUEST="guest-image"
+IMAGE_TYPE_VSPHERE="vsphere"
+
+# select cloud provider based on image type
+#
+# the supported image types are listed in the api spec (internal/cloudapi/v2/openapi.v2.yml)
+
+case ${IMAGE_TYPE} in
+    "$IMAGE_TYPE_AWS")
+        CLOUD_PROVIDER="${CLOUD_PROVIDER_AWS}"
+        ;;
+    "$IMAGE_TYPE_AZURE")
+        CLOUD_PROVIDER="${CLOUD_PROVIDER_AZURE}"
+        ;;
+    "$IMAGE_TYPE_GCP")
+        CLOUD_PROVIDER="${CLOUD_PROVIDER_GCP}"
+        ;;
+    "$IMAGE_TYPE_EDGE_COMMIT"|"$IMAGE_TYPE_EDGE_CONTAINER"|"$IMAGE_TYPE_EDGE_INSTALLER"|"$IMAGE_TYPE_IMAGE_INSTALLER"|"$IMAGE_TYPE_GUEST"|"$IMAGE_TYPE_VSPHERE")
+        # blobby image types: upload to s3 and provide download link
+        CLOUD_PROVIDER="${CLOUD_PROVIDER_AWS_S3}"
+        ;;
+    *)
+        echo "Unknown image type: ${IMAGE_TYPE}"
+        exit 1
 esac
 
+greenprint "Using Cloud Provider / Target ${CLOUD_PROVIDER} for Image Type ${IMAGE_TYPE}"
+
 #
 # Verify that this script is running in the right environment.
 #
@@ -478,7 +500,7 @@ function createReqFileAWS() {
   },
   "image_request": {
       "architecture": "$ARCH",
-      "image_type": "aws",
+      "image_type": "${IMAGE_TYPE}",
       "repositories": $(jq ".\"$ARCH\"" /usr/share/tests/osbuild-composer/repositories/"$DISTRO".json),
       "upload_options": {
         "region": "${AWS_REGION}",
@@ -502,11 +524,22 @@ function createReqFileAWSS3() {
   "customizations": {
     "packages": [
       "postgresql"
+    ],
+    "users":[
+      {
+        "name": "user1",
+        "groups": ["wheel"],
+        "key": "$(cat /tmp/usertest.pub)"
+      },
+      {
+        "name": "user2",
+        "key": "$(cat /tmp/usertest.pub)"
+      }
     ]
   },
   "image_request": {
     "architecture": "$ARCH",
-    "image_type": "edge-commit",
+    "image_type": "${IMAGE_TYPE}",
     "repositories": $(jq ".\"$ARCH\"" /usr/share/tests/osbuild-composer/repositories/"$DISTRO".json),
     "ostree": {
       "ref": "${OSTREE_REF}"
@@ -539,7 +572,7 @@ function createReqFileGCP() {
   },
   "image_request": {
     "architecture": "$ARCH",
-    "image_type": "gcp",
+    "image_type": "${IMAGE_TYPE}",
     "repositories": $(jq ".\"$ARCH\"" /usr/share/tests/osbuild-composer/repositories/"$DISTRO".json),
     "upload_options": {
       "bucket": "${GCP_BUCKET}",
@@ -565,7 +598,7 @@ function createReqFileAzure() {
   },
   "image_request": {
     "architecture": "$ARCH",
-    "image_type": "azure",
+    "image_type": "${IMAGE_TYPE}",
     "repositories": $(jq ".\"$ARCH\"" /usr/share/tests/osbuild-composer/repositories/"$DISTRO".json),
     "upload_options": {
       "tenant_id": "${AZURE_TENANT_ID}",
@@ -696,15 +729,15 @@ test "$UPLOAD_STATUS" = "success"
 test "$UPLOAD_TYPE" = "$CLOUD_PROVIDER"
 test $((INIT_COMPOSES+1)) = "$SUBS_COMPOSES"
 
+# Disable -x for these commands to avoid printing the whole result and manifest into the log
+set +x
+
 # Make sure we get 3 job entries in the db per compose (depsolve + manifest + build)
 sudo podman exec osbuild-composer-db psql -U postgres -d osbuildcomposer -c "SELECT * FROM jobs;" | grep "9 rows"
 
-#
-# Save the Manifest from the osbuild-composer store
-# NOTE: The rest of the job data can contain sensitive information
-#
-# Suppressing shellcheck.  See https://github.com/koalaman/shellcheck/wiki/SC2024#exceptions
-sudo podman exec osbuild-composer-db psql -U postgres -d osbuildcomposer -c "SELECT result->>'Manifest' FROM jobs" | sudo tee "${ARTIFACTS}/manifest.json"
+# Save the result, including the manifest, for the job, straight from the db
+sudo podman exec osbuild-composer-db psql -U postgres -d osbuildcomposer -c "SELECT result FROM jobs WHERE type='manifest-id-only'" | sudo tee "${ARTIFACTS}/build-result.txt"
+set -x
 
 #
 # Verify the Cloud-provider specific upload_status options
@@ -822,13 +855,13 @@ function verifyInAWS() {
 
   AMI_IMAGE_ID=$(jq -r '.Images[].ImageId' "$WORKDIR/ami.json")
   AWS_SNAPSHOT_ID=$(jq -r '.Images[].BlockDeviceMappings[].Ebs.SnapshotId' "$WORKDIR/ami.json")
-  
+
   # Tag image and snapshot with "gitlab-ci-test" tag
   $AWS_CMD ec2 create-tags \
     --resources "${AWS_SNAPSHOT_ID}" "${AMI_IMAGE_ID}" \
     --tags Key=gitlab-ci-test,Value=true
 
-  
+
   SHARE_OK=1
 
   # Verify that the ec2 snapshot was shared
@@ -889,53 +922,112 @@ function verifyInAWS() {
   fi
 }
 
+# verify edge commit content
+function verifyEdgeCommit() {
+    filename="$1"
+    greenprint "Verifying contents of ${filename}"
+
+    # extract tarball and save file list to artifacts directroy
+    local COMMIT_DIR
+    COMMIT_DIR="${WORKDIR}/edge-commit"
+    mkdir -p "${COMMIT_DIR}"
+    tar xvf "${filename}" -C "${COMMIT_DIR}" > "${ARTIFACTS}/edge-commit-filelist.txt"
+
+    # Verify that the commit contains the ref we defined in the request
+    sudo dnf install -y ostree
+    local COMMIT_REF
+    COMMIT_REF=$(ostree refs --repo "${COMMIT_DIR}/repo")
+    if [[ "${COMMIT_REF}" !=  "${OSTREE_REF}" ]]; then
+        echo "Commit ref in archive does not match request 😠"
+        exit 1
+    fi
 
-# Verify tarball in AWS S3
-function verifyInAWSS3() {
-  local S3_URL
-  S3_URL=$(echo "$UPLOAD_OPTIONS" | jq -r '.url')
+    local TAR_COMMIT_ID
+    TAR_COMMIT_ID=$(ostree rev-parse --repo "${COMMIT_DIR}/repo" "${OSTREE_REF}")
+    API_COMMIT_ID_V2=$(curl \
+        --silent \
+        --show-error \
+        --cacert /etc/osbuild-composer/ca-crt.pem \
+        --key /etc/osbuild-composer/client-key.pem \
+        --cert /etc/osbuild-composer/client-crt.pem \
+        https://localhost/api/image-builder-composer/v2/composes/"$COMPOSE_ID"/metadata | jq -r '.ostree_commit')
+
+    if [[ "${API_COMMIT_ID_V2}" != "${TAR_COMMIT_ID}" ]]; then
+        echo "Commit ID returned from API does not match Commit ID in archive 😠"
+        exit 1
+    fi
 
-  # Tag the resource as a test file
-  local S3_FILENAME
-  S3_FILENAME=$(echo "${S3_URL}" | grep -oP '(?<=/)[^/]+(?=\?)')
+}
 
-  $AWS_CMD s3api put-object-tagging \
-	  --bucket "${AWS_BUCKET}" \
-	  --key "${S3_FILENAME}" \
-	  --tagging '{"TagSet": [{ "Key": "gitlab-ci-test", "Value": "true" }]}'	  
-
-  # Download the commit using the Presigned URL
-  curl "${S3_URL}" --output "${WORKDIR}/edge-commit.tar"
-
-  # extract tarball and save file list to artifacts directroy
-  local COMMIT_DIR
-  COMMIT_DIR="${WORKDIR}/edge-commit"
-  mkdir -p "${COMMIT_DIR}"
-  tar xvf "${WORKDIR}/edge-commit.tar" -C "${COMMIT_DIR}" > "${ARTIFACTS}/edge-commit-filelist.txt"
-
-  # Verify that the commit contains the ref we defined in the request
-  sudo dnf install -y ostree
-  local COMMIT_REF
-  COMMIT_REF=$(ostree refs --repo "${COMMIT_DIR}/repo")
-  if [[ "${COMMIT_REF}" !=  "${OSTREE_REF}" ]]; then
-      echo "Commit ref in archive does not match request 😠"
-      exit 1
-  fi
+# Verify image blobs from s3
+function verifyDisk() {
+    filename="$1"
+    greenprint "Verifying contents of ${filename}"
+
+    infofile="${filename}-info.json"
+    sudo /usr/libexec/osbuild-composer-test/image-info "${filename}" | tee "${infofile}" > /dev/null
+    
+    # save image info to artifacts
+    cp -v "${infofile}" "${ARTIFACTS}/image-info.json"
+
+    # check compose request users in passwd
+    if ! jq .passwd "${infofile}" | grep -q "user1"; then
+        greenprint "❌ user1 not found in passwd file"
+        exit 1
+    fi
+    if ! jq .passwd "${infofile}" | grep -q "user2"; then
+        greenprint "❌ user2 not found in passwd file"
+        exit 1
+    fi
+    # check packages for postgresql
+    if ! jq .packages "${infofile}" | grep -q "postgresql"; then
+        greenprint "❌ postgresql not found in packages"
+        exit 1
+    fi
 
-  local TAR_COMMIT_ID
-  TAR_COMMIT_ID=$(ostree rev-parse --repo "${COMMIT_DIR}/repo" "${OSTREE_REF}")
-  API_COMMIT_ID_V2=$(curl \
-    --silent \
-    --show-error \
-    --cacert /etc/osbuild-composer/ca-crt.pem \
-    --key /etc/osbuild-composer/client-key.pem \
-    --cert /etc/osbuild-composer/client-crt.pem \
-    https://localhost/api/image-builder-composer/v2/composes/"$COMPOSE_ID"/metadata | jq -r '.ostree_commit')
+    greenprint "✅ ${filename} image info verified"
+}
 
-  if [[ "${API_COMMIT_ID_V2}" != "${TAR_COMMIT_ID}" ]]; then
-      echo "Commit ID returned from API does not match Commit ID in archive 😠"
-      exit 1
-  fi
+
+# Verify s3 blobs
+function verifyInAWSS3() {
+    local S3_URL
+    S3_URL=$(echo "$UPLOAD_OPTIONS" | jq -r '.url')
+    greenprint "Verifying S3 object at ${S3_URL}"
+
+    # Tag the resource as a test file
+    local S3_FILENAME
+    S3_FILENAME=$(echo "${S3_URL}" | grep -oP '(?<=/)[^/]+(?=\?)')
+
+    # tag the object, also verifying that it exists in the bucket as expected
+    $AWS_CMD s3api put-object-tagging \
+        --bucket "${AWS_BUCKET}" \
+        --key "${S3_FILENAME}" \
+        --tagging '{"TagSet": [{ "Key": "gitlab-ci-test", "Value": "true" }]}'
+
+    greenprint "✅ Successfully tagged S3 object"
+
+    # Download the object using the Presigned URL and inspect
+    case ${IMAGE_TYPE} in
+        "$IMAGE_TYPE_EDGE_COMMIT")
+            curl "${S3_URL}" --output "${WORKDIR}/edge-commit.tar"
+            verifyEdgeCommit "${WORKDIR}/edge-commit.tar"
+            ;;
+        "${IMAGE_TYPE_GUEST}")
+            curl "${S3_URL}" --output "${WORKDIR}/disk.qcow2"
+            verifyDisk "${WORKDIR}/disk.qcow2"
+            ;;
+
+        "${IMAGE_TYPE_VSPHERE}")
+            curl "${S3_URL}" --output "${WORKDIR}/disk.vmdk"
+            verifyDisk "${WORKDIR}/disk.vmdk"
+            ;;
+        *)
+            greenprint "No validation method for image type ${IMAGE_TYPE}"
+            ;;
+    esac
+
+    greenprint "✅ Successfully verified S3 object"
 }
 
 # Verify image in Compute Engine on GCP
@@ -1027,7 +1119,7 @@ function verifyInAzure() {
       --vnet-name "vnet-$TEST_ID" \
       --network-security-group "nsg-$TEST_ID" \
       --public-ip-address "ip-$TEST_ID" \
-      --location "$AZURE_LOCATION" 
+      --location "$AZURE_LOCATION"
 
   # create the instance
   AZURE_INSTANCE_NAME="vm-$TEST_ID"
diff --git a/test/data/repositories/rhel-90-beta.json b/test/data/repositories/rhel-90-beta.json
index 59a0b7c94..2cb1cfeb6 100644
--- a/test/data/repositories/rhel-90-beta.json
+++ b/test/data/repositories/rhel-90-beta.json
@@ -2,53 +2,53 @@
   "aarch64": [
     {
       "name": "baseos",
-      "baseurl": "http://download.devel.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9.0/compose/BaseOS/aarch64/os",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-baseos-n9.0-20211203/",
       "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n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\nvt61kpkXoRGxjTlc6h+69aidSg==\n=ls8J\n-----END PGP PUBLIC KEY BLOCK-----\n"
     },
     {
       "name": "appstream",
-      "baseurl": "http://download.devel.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9.0/compose/AppStream/aarch64/os",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-aarch64-appstream-n9.0-20211203/",
       "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n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\nvt61kpkXoRGxjTlc6h+69aidSg==\n=ls8J\n-----END PGP PUBLIC KEY BLOCK-----\n"
     }
   ],
   "ppc64le": [
     {
       "name": "baseos",
-      "baseurl": "http://download.devel.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9.0/compose/BaseOS/ppc64le/os",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-ppc64le-baseos-n9.0-20211203/",
       "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFsy23UBEACUKSphFEIEvNpy68VeW4Dt6qv+mU6am9a2AAl10JANLj1oqWX+\noYk3en1S6cVe2qehSL5DGVa3HMUZkP3dtbD4SgzXzxPodebPcr4+0QNWigkUisri\nXGL5SCEcOP30zDhZvg+4mpO2jMi7Kc1DLPzBBkgppcX91wa0L1pQzBcvYMPyV/Dh\nKbQHR75WdkP6OA2JXdfC94nxYq+2e0iPqC1hCP3Elh+YnSkOkrawDPmoB1g4+ft/\nxsiVGVy/W0ekXmgvYEHt6si6Y8NwXgnTMqxeSXQ9YUgVIbTpsxHQKGy76T5lMlWX\n4LCOmEVomBJg1SqF6yi9Vu8TeNThaDqT4/DddYInd0OO69s0kGIXalVgGYiW2HOD\nx2q5R1VGCoJxXomz+EbOXY+HpKPOHAjU0DB9MxbU3S248LQ69nIB5uxysy0PSco1\nsdZ8sxRNQ9Dw6on0Nowx5m6Thefzs5iK3dnPGBqHTT43DHbnWc2scjQFG+eZhe98\nEll/kb6vpBoY4bG9/wCG9qu7jj9Z+BceCNKeHllbezVLCU/Hswivr7h2dnaEFvPD\nO4GqiWiwOF06XaBMVgxA8p2HRw0KtXqOpZk+o+sUvdPjsBw42BB96A1yFX4jgFNA\nPyZYnEUdP6OOv9HSjnl7k/iEkvHq/jGYMMojixlvXpGXhnt5jNyc4GSUJQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChhdXhpbGlhcnkga2V5KSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjkEEwECACMFAlsy23UCGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIX\ngAAKCRD3b2bD1AgnknqOD/9fB2ASuG2aJIiap4kK58R+RmOVM4qgclAnaG57+vjI\nnKvyfV3NH/keplGNRxwqHekfPCqvkpABwhdGEXIE8ILqnPewIMr6PZNZWNJynZ9i\neSMzVuCG7jDoGyQ5/6B0f6xeBtTeBDiRl7+Alehet1twuGL1BJUYG0QuLgcEzkaE\n/gkuumeVcazLzz7L12D22nMk66GxmgXfqS5zcbqOAuZwaA6VgSEgFdV2X2JU79zS\nBQJXv7NKc+nDXFG7M7EHjY3Rma3HXkDbkT8bzh9tJV7Z7TlpT829pStWQyoxKCVq\nsEX8WsSapTKA3P9YkYCwLShgZu4HKRFvHMaIasSIZWzLu+RZH/4yyHOhj0QB7XMY\neHQ6fGSbtJ+K6SrpHOOsKQNAJ0hVbSrnA1cr5+2SDfel1RfYt0W9FA6DoH/S5gAR\ndzT1u44QVwwp3U+eFpHphFy//uzxNMtCjjdkpzhYYhOCLNkDrlRPb+bcoL/6ePSr\n016PA7eEnuC305YU1Ml2WcCn7wQV8x90o33klJmEkWtXh3X39vYtI4nCPIvZn1eP\nVy+F+wWt4vN2b8oOdlzc2paOembbCo2B+Wapv5Y9peBvlbsDSgqtJABfK8KQq/jK\nYl3h5elIa1I3uNfczeHOnf1enLOUOlq630yeM/yHizz99G1g+z/guMh5+x/OHraW\niLkCDQRbMtt1ARAA1lNsWklhS9LoBdolTVtg65FfdFJr47pzKRGYIoGLbcJ155ND\nG+P8UrM06E/ah06EEWuvu2YyyYAz1iYGsCwHAXtbEJh+1tF0iOVx2vnZPgtIGE9V\nP95V5ZvWvB3bdke1z8HadDA+/Ve7fbwXXLa/z9QhSQgsJ8NS8KYnDDjI4EvQtv0i\nPVLY8+u8z6VyiV9RJyn8UEZEJdbFDF9AZAT8103w8SEo/cvIoUbVKZLGcXdAIjCa\ny04u6jsrMp9UGHZX7+srT+9YHDzQixei4IdmxUcqtiNR2/bFHpHCu1pzYjXj968D\n8Ng2txBXDgs16BF/9l++GWKz2dOSH0jdS6sFJ/Dmg7oYnJ2xKSJEmcnV8Z0M1n4w\nXR1t/KeKZe3aR+RXCAEVC5dQ3GbRW2+WboJ6ldgFcVcOv6iOSWP9TrLzFPOpCsIr\nnHE+cMBmPHq3dUm7KeYXQ6wWWmtXlw6widf7cBcGFeELpuU9klzqdKze8qo2oMkf\nrfxIq8zdciPxZXb/75dGWs6dLHQmDpo4MdQVskw5vvwHicMpUpGpxkX7X1XAfdQf\nyIHLGT4ZXuMLIMUPdzJE0Vwt/RtJrZ+feLSv/+0CkkpGHORYroGwIBrJ2RikgcV2\nbc98V/27Kz2ngUCEwnmlhIcrY4IGAAZzUAl0GLHSevPbAREu4fDW4Y+ztOsAEQEA\nAYkCHwQYAQIACQUCWzLbdQIbDAAKCRD3b2bD1AgnkusfD/9U4sPtZfMw6cII167A\nXRZOO195G7oiAnBUw5AW6EK0SAHVZcuW0LMMXnGe9f4UsEUgCNwo5mvLWPxzKqFq\n6/G3kEZVFwZ0qrlLoJPeHNbOcfkeZ9NgD/OhzQmdylM0IwGM9DMrm2YS4EVsmm2b\n53qKIfIyysp1yAGcTnBwBbZ85osNBl2KRDIPhMs0bnmGB7IAvwlSb+xm6vWKECkO\nlwQDO5Kg8YZ8+Z3pn/oS688t/fPXvWLZYUqwR63oWfIaPJI7Ahv2jJmgw1ofL81r\n2CE3T/OydtUeGLzqWJAB8sbUgT3ug0cjtxsHuroQBSYBND3XDb/EQh5GeVVnGKKH\ngESLFAoweoNjDSXrlIu1gFjCDHF4CqBRmNYKrNQjLmhCrSfwkytXESJwlLzFKY8P\nK1yZyTpDC9YK0G7qgrk7EHmH9JAZTQ5V65pp0vR9KvqTU5ewkQDIljD2f3FIqo2B\nSKNCQE+N6NjWaTeNlU75m+yZocKObSPg0zS8FAuSJetNtzXA7ouqk34OoIMQj4gq\nUnh/i1FcZAd4U6Dtr9aRZ6PeLlm6MJ/h582L6fJLNEu136UWDtJj5eBYEzX13l+d\nSC4PEHx7ZZRwQKptl9NkinLZGJztg175paUu8C34sAv+SQnM20c0pdOXAq9GKKhi\nvt61kpkXoRGxjTlc6h+69aidSg==\n=ls8J\n-----END PGP PUBLIC KEY BLOCK-----\n"
     },
     {
       "name": "appstream",
-      "baseurl": "http://download.devel.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9.0/compose/AppStream/ppc64le/os",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-ppc64le-appstream-n9.0-20211203/",
       "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n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\nvt61kpkXoRGxjTlc6h+69aidSg==\n=ls8J\n-----END PGP PUBLIC KEY BLOCK-----\n"
     }
   ],
   "s390x": [
     {
       "name": "baseos",
-      "baseurl": "http://download.devel.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9.0/compose/BaseOS/s390x/os",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-s390x-baseos-n9.0-20211203/",
       "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFsy23UBEACUKSphFEIEvNpy68VeW4Dt6qv+mU6am9a2AAl10JANLj1oqWX+\noYk3en1S6cVe2qehSL5DGVa3HMUZkP3dtbD4SgzXzxPodebPcr4+0QNWigkUisri\nXGL5SCEcOP30zDhZvg+4mpO2jMi7Kc1DLPzBBkgppcX91wa0L1pQzBcvYMPyV/Dh\nKbQHR75WdkP6OA2JXdfC94nxYq+2e0iPqC1hCP3Elh+YnSkOkrawDPmoB1g4+ft/\nxsiVGVy/W0ekXmgvYEHt6si6Y8NwXgnTMqxeSXQ9YUgVIbTpsxHQKGy76T5lMlWX\n4LCOmEVomBJg1SqF6yi9Vu8TeNThaDqT4/DddYInd0OO69s0kGIXalVgGYiW2HOD\nx2q5R1VGCoJxXomz+EbOXY+HpKPOHAjU0DB9MxbU3S248LQ69nIB5uxysy0PSco1\nsdZ8sxRNQ9Dw6on0Nowx5m6Thefzs5iK3dnPGBqHTT43DHbnWc2scjQFG+eZhe98\nEll/kb6vpBoY4bG9/wCG9qu7jj9Z+BceCNKeHllbezVLCU/Hswivr7h2dnaEFvPD\nO4GqiWiwOF06XaBMVgxA8p2HRw0KtXqOpZk+o+sUvdPjsBw42BB96A1yFX4jgFNA\nPyZYnEUdP6OOv9HSjnl7k/iEkvHq/jGYMMojixlvXpGXhnt5jNyc4GSUJQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChhdXhpbGlhcnkga2V5KSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjkEEwECACMFAlsy23UCGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIX\ngAAKCRD3b2bD1AgnknqOD/9fB2ASuG2aJIiap4kK58R+RmOVM4qgclAnaG57+vjI\nnKvyfV3NH/keplGNRxwqHekfPCqvkpABwhdGEXIE8ILqnPewIMr6PZNZWNJynZ9i\neSMzVuCG7jDoGyQ5/6B0f6xeBtTeBDiRl7+Alehet1twuGL1BJUYG0QuLgcEzkaE\n/gkuumeVcazLzz7L12D22nMk66GxmgXfqS5zcbqOAuZwaA6VgSEgFdV2X2JU79zS\nBQJXv7NKc+nDXFG7M7EHjY3Rma3HXkDbkT8bzh9tJV7Z7TlpT829pStWQyoxKCVq\nsEX8WsSapTKA3P9YkYCwLShgZu4HKRFvHMaIasSIZWzLu+RZH/4yyHOhj0QB7XMY\neHQ6fGSbtJ+K6SrpHOOsKQNAJ0hVbSrnA1cr5+2SDfel1RfYt0W9FA6DoH/S5gAR\ndzT1u44QVwwp3U+eFpHphFy//uzxNMtCjjdkpzhYYhOCLNkDrlRPb+bcoL/6ePSr\n016PA7eEnuC305YU1Ml2WcCn7wQV8x90o33klJmEkWtXh3X39vYtI4nCPIvZn1eP\nVy+F+wWt4vN2b8oOdlzc2paOembbCo2B+Wapv5Y9peBvlbsDSgqtJABfK8KQq/jK\nYl3h5elIa1I3uNfczeHOnf1enLOUOlq630yeM/yHizz99G1g+z/guMh5+x/OHraW\niLkCDQRbMtt1ARAA1lNsWklhS9LoBdolTVtg65FfdFJr47pzKRGYIoGLbcJ155ND\nG+P8UrM06E/ah06EEWuvu2YyyYAz1iYGsCwHAXtbEJh+1tF0iOVx2vnZPgtIGE9V\nP95V5ZvWvB3bdke1z8HadDA+/Ve7fbwXXLa/z9QhSQgsJ8NS8KYnDDjI4EvQtv0i\nPVLY8+u8z6VyiV9RJyn8UEZEJdbFDF9AZAT8103w8SEo/cvIoUbVKZLGcXdAIjCa\ny04u6jsrMp9UGHZX7+srT+9YHDzQixei4IdmxUcqtiNR2/bFHpHCu1pzYjXj968D\n8Ng2txBXDgs16BF/9l++GWKz2dOSH0jdS6sFJ/Dmg7oYnJ2xKSJEmcnV8Z0M1n4w\nXR1t/KeKZe3aR+RXCAEVC5dQ3GbRW2+WboJ6ldgFcVcOv6iOSWP9TrLzFPOpCsIr\nnHE+cMBmPHq3dUm7KeYXQ6wWWmtXlw6widf7cBcGFeELpuU9klzqdKze8qo2oMkf\nrfxIq8zdciPxZXb/75dGWs6dLHQmDpo4MdQVskw5vvwHicMpUpGpxkX7X1XAfdQf\nyIHLGT4ZXuMLIMUPdzJE0Vwt/RtJrZ+feLSv/+0CkkpGHORYroGwIBrJ2RikgcV2\nbc98V/27Kz2ngUCEwnmlhIcrY4IGAAZzUAl0GLHSevPbAREu4fDW4Y+ztOsAEQEA\nAYkCHwQYAQIACQUCWzLbdQIbDAAKCRD3b2bD1AgnkusfD/9U4sPtZfMw6cII167A\nXRZOO195G7oiAnBUw5AW6EK0SAHVZcuW0LMMXnGe9f4UsEUgCNwo5mvLWPxzKqFq\n6/G3kEZVFwZ0qrlLoJPeHNbOcfkeZ9NgD/OhzQmdylM0IwGM9DMrm2YS4EVsmm2b\n53qKIfIyysp1yAGcTnBwBbZ85osNBl2KRDIPhMs0bnmGB7IAvwlSb+xm6vWKECkO\nlwQDO5Kg8YZ8+Z3pn/oS688t/fPXvWLZYUqwR63oWfIaPJI7Ahv2jJmgw1ofL81r\n2CE3T/OydtUeGLzqWJAB8sbUgT3ug0cjtxsHuroQBSYBND3XDb/EQh5GeVVnGKKH\ngESLFAoweoNjDSXrlIu1gFjCDHF4CqBRmNYKrNQjLmhCrSfwkytXESJwlLzFKY8P\nK1yZyTpDC9YK0G7qgrk7EHmH9JAZTQ5V65pp0vR9KvqTU5ewkQDIljD2f3FIqo2B\nSKNCQE+N6NjWaTeNlU75m+yZocKObSPg0zS8FAuSJetNtzXA7ouqk34OoIMQj4gq\nUnh/i1FcZAd4U6Dtr9aRZ6PeLlm6MJ/h582L6fJLNEu136UWDtJj5eBYEzX13l+d\nSC4PEHx7ZZRwQKptl9NkinLZGJztg175paUu8C34sAv+SQnM20c0pdOXAq9GKKhi\nvt61kpkXoRGxjTlc6h+69aidSg==\n=ls8J\n-----END PGP PUBLIC KEY BLOCK-----\n"
     },
     {
       "name": "appstream",
-      "baseurl": "http://download.devel.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9.0/compose/AppStream/s390x/os",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-s390x-appstream-n9.0-20211203/",
       "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n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\nvt61kpkXoRGxjTlc6h+69aidSg==\n=ls8J\n-----END PGP PUBLIC KEY BLOCK-----\n"
     }
   ],
   "x86_64": [
     {
       "name": "baseos",
-      "baseurl": "http://download.devel.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9.0/compose/BaseOS/x86_64/os",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-baseos-n9.0-20211203/",
       "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFsy23UBEACUKSphFEIEvNpy68VeW4Dt6qv+mU6am9a2AAl10JANLj1oqWX+\noYk3en1S6cVe2qehSL5DGVa3HMUZkP3dtbD4SgzXzxPodebPcr4+0QNWigkUisri\nXGL5SCEcOP30zDhZvg+4mpO2jMi7Kc1DLPzBBkgppcX91wa0L1pQzBcvYMPyV/Dh\nKbQHR75WdkP6OA2JXdfC94nxYq+2e0iPqC1hCP3Elh+YnSkOkrawDPmoB1g4+ft/\nxsiVGVy/W0ekXmgvYEHt6si6Y8NwXgnTMqxeSXQ9YUgVIbTpsxHQKGy76T5lMlWX\n4LCOmEVomBJg1SqF6yi9Vu8TeNThaDqT4/DddYInd0OO69s0kGIXalVgGYiW2HOD\nx2q5R1VGCoJxXomz+EbOXY+HpKPOHAjU0DB9MxbU3S248LQ69nIB5uxysy0PSco1\nsdZ8sxRNQ9Dw6on0Nowx5m6Thefzs5iK3dnPGBqHTT43DHbnWc2scjQFG+eZhe98\nEll/kb6vpBoY4bG9/wCG9qu7jj9Z+BceCNKeHllbezVLCU/Hswivr7h2dnaEFvPD\nO4GqiWiwOF06XaBMVgxA8p2HRw0KtXqOpZk+o+sUvdPjsBw42BB96A1yFX4jgFNA\nPyZYnEUdP6OOv9HSjnl7k/iEkvHq/jGYMMojixlvXpGXhnt5jNyc4GSUJQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChhdXhpbGlhcnkga2V5KSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjkEEwECACMFAlsy23UCGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIX\ngAAKCRD3b2bD1AgnknqOD/9fB2ASuG2aJIiap4kK58R+RmOVM4qgclAnaG57+vjI\nnKvyfV3NH/keplGNRxwqHekfPCqvkpABwhdGEXIE8ILqnPewIMr6PZNZWNJynZ9i\neSMzVuCG7jDoGyQ5/6B0f6xeBtTeBDiRl7+Alehet1twuGL1BJUYG0QuLgcEzkaE\n/gkuumeVcazLzz7L12D22nMk66GxmgXfqS5zcbqOAuZwaA6VgSEgFdV2X2JU79zS\nBQJXv7NKc+nDXFG7M7EHjY3Rma3HXkDbkT8bzh9tJV7Z7TlpT829pStWQyoxKCVq\nsEX8WsSapTKA3P9YkYCwLShgZu4HKRFvHMaIasSIZWzLu+RZH/4yyHOhj0QB7XMY\neHQ6fGSbtJ+K6SrpHOOsKQNAJ0hVbSrnA1cr5+2SDfel1RfYt0W9FA6DoH/S5gAR\ndzT1u44QVwwp3U+eFpHphFy//uzxNMtCjjdkpzhYYhOCLNkDrlRPb+bcoL/6ePSr\n016PA7eEnuC305YU1Ml2WcCn7wQV8x90o33klJmEkWtXh3X39vYtI4nCPIvZn1eP\nVy+F+wWt4vN2b8oOdlzc2paOembbCo2B+Wapv5Y9peBvlbsDSgqtJABfK8KQq/jK\nYl3h5elIa1I3uNfczeHOnf1enLOUOlq630yeM/yHizz99G1g+z/guMh5+x/OHraW\niLkCDQRbMtt1ARAA1lNsWklhS9LoBdolTVtg65FfdFJr47pzKRGYIoGLbcJ155ND\nG+P8UrM06E/ah06EEWuvu2YyyYAz1iYGsCwHAXtbEJh+1tF0iOVx2vnZPgtIGE9V\nP95V5ZvWvB3bdke1z8HadDA+/Ve7fbwXXLa/z9QhSQgsJ8NS8KYnDDjI4EvQtv0i\nPVLY8+u8z6VyiV9RJyn8UEZEJdbFDF9AZAT8103w8SEo/cvIoUbVKZLGcXdAIjCa\ny04u6jsrMp9UGHZX7+srT+9YHDzQixei4IdmxUcqtiNR2/bFHpHCu1pzYjXj968D\n8Ng2txBXDgs16BF/9l++GWKz2dOSH0jdS6sFJ/Dmg7oYnJ2xKSJEmcnV8Z0M1n4w\nXR1t/KeKZe3aR+RXCAEVC5dQ3GbRW2+WboJ6ldgFcVcOv6iOSWP9TrLzFPOpCsIr\nnHE+cMBmPHq3dUm7KeYXQ6wWWmtXlw6widf7cBcGFeELpuU9klzqdKze8qo2oMkf\nrfxIq8zdciPxZXb/75dGWs6dLHQmDpo4MdQVskw5vvwHicMpUpGpxkX7X1XAfdQf\nyIHLGT4ZXuMLIMUPdzJE0Vwt/RtJrZ+feLSv/+0CkkpGHORYroGwIBrJ2RikgcV2\nbc98V/27Kz2ngUCEwnmlhIcrY4IGAAZzUAl0GLHSevPbAREu4fDW4Y+ztOsAEQEA\nAYkCHwQYAQIACQUCWzLbdQIbDAAKCRD3b2bD1AgnkusfD/9U4sPtZfMw6cII167A\nXRZOO195G7oiAnBUw5AW6EK0SAHVZcuW0LMMXnGe9f4UsEUgCNwo5mvLWPxzKqFq\n6/G3kEZVFwZ0qrlLoJPeHNbOcfkeZ9NgD/OhzQmdylM0IwGM9DMrm2YS4EVsmm2b\n53qKIfIyysp1yAGcTnBwBbZ85osNBl2KRDIPhMs0bnmGB7IAvwlSb+xm6vWKECkO\nlwQDO5Kg8YZ8+Z3pn/oS688t/fPXvWLZYUqwR63oWfIaPJI7Ahv2jJmgw1ofL81r\n2CE3T/OydtUeGLzqWJAB8sbUgT3ug0cjtxsHuroQBSYBND3XDb/EQh5GeVVnGKKH\ngESLFAoweoNjDSXrlIu1gFjCDHF4CqBRmNYKrNQjLmhCrSfwkytXESJwlLzFKY8P\nK1yZyTpDC9YK0G7qgrk7EHmH9JAZTQ5V65pp0vR9KvqTU5ewkQDIljD2f3FIqo2B\nSKNCQE+N6NjWaTeNlU75m+yZocKObSPg0zS8FAuSJetNtzXA7ouqk34OoIMQj4gq\nUnh/i1FcZAd4U6Dtr9aRZ6PeLlm6MJ/h582L6fJLNEu136UWDtJj5eBYEzX13l+d\nSC4PEHx7ZZRwQKptl9NkinLZGJztg175paUu8C34sAv+SQnM20c0pdOXAq9GKKhi\nvt61kpkXoRGxjTlc6h+69aidSg==\n=ls8J\n-----END PGP PUBLIC KEY BLOCK-----\n"
     },
     {
       "name": "appstream",
-      "baseurl": "http://download.devel.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9.0/compose/AppStream/x86_64/os",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-appstream-n9.0-20211203/",
       "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n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\nvt61kpkXoRGxjTlc6h+69aidSg==\n=ls8J\n-----END PGP PUBLIC KEY BLOCK-----\n"
     },
     {
       "name": "rt",
-      "baseurl": "http://download.devel.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9.0/compose/RT/x86_64/os",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-rt-n9.0-20211203/",
       "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF\n0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF\n0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c\nu7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh\nXGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H\n5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW\n9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj\n/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1\nPcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY\nHVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF\nbuhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB\ntDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0\nLmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK\nCRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC\n2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf\nC/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5\nun3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E\n0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE\nIGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh\n8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL\nGht5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki\nJUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25\nOFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq\ndzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==\n=zbHE\n-----END PGP PUBLIC KEY BLOCK-----\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n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\nvt61kpkXoRGxjTlc6h+69aidSg==\n=ls8J\n-----END PGP PUBLIC KEY BLOCK-----\n"
     }
   ]