Repository: osbuild/osbuild-composer
Pull request #2844 (2022-07-19 18:12:20)

Support cloud upload for Koji composes
This PR changes the Cloud API implementation and relevant worker jobs to allow specifying cloud upload target for Koji composes. Koji composes are always uploaded to Koji (the target is added always implicitly to the image request), however with this change, an additional cloud target can be specified as well.

### Notable changes

- Allow specifying upload options in Cloud API for Koji composes.
- Support `gce-rhui` image type in Cloud API, which was not supported so far.
- Enhance the `koji-finalize` job implementation to handle more than one upload target for koji composes.
- Enhance the `koji.sh` test case to also test Koji composes with cloud upload.
- Test cloud upload with Koji composes in CI for all supported cloud providers and their respective RHUI images.
- The `osbuild` job implementation now contains a workaround to allow uploading XZ compressed images to AWS and Azure. This is done by checking if the image filename is suffixed with `.xz` before uploading it and if it is, the implementation extracts it. The workaround will be needed until multiple exports are supported by image definitions and different exports are used per upload target (`osbuild` job implementation already supports this).

- [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`

Information regarding our GitLab pipeline (Schutzbot):

CI will not be ran automatically if WIP label is applied or the PR is in DRAFT state, instead only
a link will be provided to the pipeline which can then be triggered manually if desired. To run the
CI automatically either switch the PR to ready or apply WIP+test label.

Outside contributors need manual approval from one of the osbuild-composer maintainers.

Schutzbot will only be triggered if all Tests jobs in GitHub workflow succeed.
-->



Patch:
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0ca377030..3d41b81cc 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -435,6 +435,36 @@ Rebase OSTree:
       - *fedora_runners
       - *rhel_runners
 
+koji.sh (cloud upload):
+  stage: test
+  extends: .terraform
+  rules:
+    - !reference [.upstream_rules, rules]
+    - !reference [.nightly_rules, rules]
+  script:
+    - schutzbot/deploy.sh
+    - /usr/libexec/tests/osbuild-composer/koji.sh cloud-upload ${CLOUD_TARGET} ${IMAGE_TYPE}
+  parallel:
+    matrix:
+      - RUNNER:
+          # Brew workers use RHEL-8.6
+          - aws/rhel-8.6-ga-x86_64
+        INTERNAL_NETWORK: ["true"]
+        CLOUD_TARGET: aws
+        IMAGE_TYPE: aws-rhui
+      - RUNNER:
+          # Brew workers use RHEL-8.6
+          - aws/rhel-8.6-ga-x86_64
+        INTERNAL_NETWORK: ["true"]
+        CLOUD_TARGET: azure
+        IMAGE_TYPE: azure-rhui
+      - RUNNER:
+          # Brew workers use RHEL-8.6
+          - aws/rhel-8.6-ga-x86_64
+        INTERNAL_NETWORK: ["true"]
+        CLOUD_TARGET: gcp
+        IMAGE_TYPE: gcp-rhui
+
 koji.sh (cloudapi):
   extends: .integration
   variables:
diff --git a/cmd/osbuild-worker/jobimpl-koji-finalize.go b/cmd/osbuild-worker/jobimpl-koji-finalize.go
index b8da7d6b8..723d12d6f 100644
--- a/cmd/osbuild-worker/jobimpl-koji-finalize.go
+++ b/cmd/osbuild-worker/jobimpl-koji-finalize.go
@@ -84,9 +84,34 @@ func (impl *KojiFinalizeJobImpl) kojiFail(server string, buildID int, token stri
 }
 
 func (impl *KojiFinalizeJobImpl) Run(job worker.Job) error {
-	var args worker.KojiFinalizeJob
-	err := job.Args(&args)
+	logWithId := logrus.WithField("jobId", job.Id().String())
+
+	// initialize the result variable to be used to report status back to composer
+	var kojiFinalizeJobResult = &worker.KojiFinalizeJobResult{}
+	// initialize / declare variables to be used to report information back to Koji
+	var args = &worker.KojiFinalizeJob{}
+	var initArgs *worker.KojiInitJobResult
+
+	// In all cases it is necessary to report result back to osbuild-composer worker API.
+	defer func() {
+		err := job.Update(kojiFinalizeJobResult)
+		if err != nil {
+			logWithId.Errorf("Error reporting job result: %v", err)
+		}
+
+		// Fail the Koji build if the job error is set and the necessary
+		// information to identify the job are available.
+		if kojiFinalizeJobResult.JobError != nil && initArgs != nil {
+			err = impl.kojiFail(args.Server, int(initArgs.BuildID), initArgs.Token)
+			if err != nil {
+				logWithId.Errorf("Failing Koji job failed: %v", err)
+			}
+		}
+	}()
+
+	err := job.Args(args)
 	if err != nil {
+		kojiFinalizeJobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorParsingJobArgs, "Error parsing job args", err.Error())
 		return err
 	}
 
@@ -99,30 +124,20 @@ func (impl *KojiFinalizeJobImpl) Run(job worker.Job) error {
 		EndTime:   time.Now().Unix(),
 	}
 
-	var initArgs *worker.KojiInitJobResult
 	var buildRoots []koji.BuildRoot
 	var images []koji.Image
 
 	var osbuildResults []worker.OSBuildJobResult
 	initArgs, osbuildResults, err = extractDynamicArgs(job)
 	if err != nil {
+		kojiFinalizeJobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorParsingDynamicArgs, "Error parsing dynamic args", err.Error())
 		return err
 	}
 	build.BuildID = initArgs.BuildID
 
-	// Check the dependencies early. Fail the koji build if any of them failed.
+	// Check the dependencies early.
 	if hasFailedDependency(*initArgs, osbuildResults) {
-		err = impl.kojiFail(args.Server, int(initArgs.BuildID), initArgs.Token)
-
-		// Update the status immediately and bail out.
-		var result worker.KojiFinalizeJobResult
-		if err != nil {
-			result.JobError = clienterrors.WorkerClientError(clienterrors.ErrorKojiFailedDependency, err.Error())
-		}
-		err = job.Update(&result)
-		if err != nil {
-			return fmt.Errorf("Error reporting job result: %v", err)
-		}
+		kojiFinalizeJobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorKojiFailedDependency, "At least one job dependency failed")
 		return nil
 	}
 
@@ -138,13 +153,15 @@ func (impl *KojiFinalizeJobImpl) Run(job worker.Job) error {
 		// multiple
 		buildRPMs = rpmmd.DeduplicateRPMs(buildRPMs)
 
-		// TODO: support multiple upload targets
-		if len(buildArgs.TargetResults) != 1 {
-			// TODO: should we call kojiFail() and update job status, instead of just returning?
-			return fmt.Errorf("error: Koji compose OSBuild job result doesn't contain exactly one target result")
+		kojiTargetResults := buildArgs.TargetResultsByName(target.TargetNameKoji)
+		// Only a single Koji target is allowed per osbuild job
+		if len(kojiTargetResults) != 1 {
+			kojiFinalizeJobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorKojiFinalize, "Exactly one Koji target results are expected per osbuild job")
+			return nil
 		}
-		kojiTarget := buildArgs.TargetResults[0]
-		kojiTargetOptions := kojiTarget.Options.(*target.KojiTargetResultOptions)
+
+		kojiTargetResult := kojiTargetResults[0]
+		kojiTargetOptions := kojiTargetResult.Options.(*target.KojiTargetResultOptions)
 
 		buildRoots = append(buildRoots, koji.BuildRoot{
 			ID: uint64(i),
@@ -195,11 +212,7 @@ func (impl *KojiFinalizeJobImpl) Run(job worker.Job) error {
 	err = impl.kojiImport(args.Server, build, buildRoots, images, args.KojiDirectory, initArgs.Token)
 	if err != nil {
 		result.JobError = clienterrors.WorkerClientError(clienterrors.ErrorKojiFinalize, err.Error())
-	}
-
-	err = job.Update(&result)
-	if err != nil {
-		return fmt.Errorf("Error reporting job result: %v", err)
+		return err
 	}
 
 	return nil
diff --git a/cmd/osbuild-worker/jobimpl-osbuild.go b/cmd/osbuild-worker/jobimpl-osbuild.go
index 25185c168..2733291a0 100644
--- a/cmd/osbuild-worker/jobimpl-osbuild.go
+++ b/cmd/osbuild-worker/jobimpl-osbuild.go
@@ -9,6 +9,7 @@ import (
 	"math/big"
 	"net/url"
 	"os"
+	"os/exec"
 	"path"
 	"strings"
 
@@ -418,7 +419,22 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
 			if impl.AWSBucket != "" {
 				bucket = impl.AWSBucket
 			}
-			_, err = a.Upload(path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename), bucket, key)
+
+			// TODO: Remove this once multiple exports will be supported and used by image definitions
+			// RHUI images tend to be produced as archives in Brew to save disk space,
+			// however they can't be imported to the cloud provider as an archive.
+			// Workaround this situation for Koji composes by checking if the image file
+			// is an archive and if it is, extract it before uploading to the cloud.
+			imagePath := path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename)
+			if strings.HasSuffix(imagePath, ".xz") {
+				imagePath, err = extractXzArchive(imagePath)
+				if err != nil {
+					targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorTargetError, "Failed to extract compressed image", err.Error())
+					break
+				}
+			}
+
+			_, err = a.Upload(imagePath, bucket, key)
 			if err != nil {
 				targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, err.Error())
 				break
@@ -614,6 +630,20 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
 			// Azure cannot create an image from a blob without .vhd extension
 			blobName := azure.EnsureVHDExtension(jobTarget.ImageName)
 
+			// TODO: Remove this once multiple exports will be supported and used by image definitions
+			// RHUI images tend to be produced as archives in Brew to save disk space,
+			// however they can't be imported to the cloud provider as an archive.
+			// Workaround this situation for Koji composes by checking if the image file
+			// is an archive and if it is, extract it before uploading to the cloud.
+			imagePath := path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename)
+			if strings.HasSuffix(imagePath, ".xz") {
+				imagePath, err = extractXzArchive(imagePath)
+				if err != nil {
+					targetResult.TargetError = clienterrors.WorkerClientError(clienterrors.ErrorTargetError, "Failed to extract compressed image", err.Error())
+					break
+				}
+			}
+
 			logWithId.Info("[Azure] ⬆ Uploading the image")
 			err = azureStorageClient.UploadPageBlob(
 				azure.BlobMetadata{
@@ -621,7 +651,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
 					ContainerName:  storageContainer,
 					BlobName:       blobName,
 				},
-				path.Join(outputDirectory, jobTarget.OsbuildArtifact.ExportName, jobTarget.OsbuildArtifact.ExportFilename),
+				imagePath,
 				azure.DefaultUploadThreads,
 			)
 			if err != nil {
@@ -790,3 +820,19 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
 
 	return nil
 }
+
+// extractXzArchive extracts the provided XZ archive in the same directory
+// and returns the path to decompressed file.
+func extractXzArchive(archivePath string) (string, error) {
+	workingDir, archiveFilename := path.Split(archivePath)
+	decompressedFilename := strings.TrimSuffix(archiveFilename, ".xz")
+
+	cmd := exec.Command("xz", "-d", archivePath)
+	cmd.Dir = workingDir
+	err := cmd.Run()
+	if err != nil {
+		return "", err
+	}
+
+	return path.Join(workingDir, decompressedFilename), nil
+}
diff --git a/internal/cloudapi/v2/handler.go b/internal/cloudapi/v2/handler.go
index 10c2633a2..111f99717 100644
--- a/internal/cloudapi/v2/handler.go
+++ b/internal/cloudapi/v2/handler.go
@@ -299,10 +299,6 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
 				return HTTPError(ErrorJSONUnMarshallingError)
 			}
 		} else {
-			// TODO: support uploads also for koji
-			if request.Koji != nil {
-				return HTTPError(ErrorJSONUnMarshallingError)
-			}
 			/* oneOf is not supported by the openapi generator so marshal and unmarshal the uploadrequest based on the type */
 			switch ir.ImageType {
 			case ImageTypesAws:
@@ -371,6 +367,8 @@ func (h *apiHandlers) PostCompose(ctx echo.Context) error {
 
 				irTarget = t
 			case ImageTypesGcp:
+				fallthrough
+			case ImageTypesGcpRhui:
 				var gcpUploadOptions GCPUploadOptions
 				jsonUploadOptions, err := json.Marshal(*ir.UploadOptions)
 				if err != nil {
@@ -485,6 +483,8 @@ func imageTypeFromApiImageType(it ImageTypes, arch distro.Arch) string {
 		return "ec2-sap"
 	case ImageTypesGcp:
 		return "gce"
+	case ImageTypesGcpRhui:
+		return "gce-rhui"
 	case ImageTypesAzure:
 		return "vhd"
 	case ImageTypesAzureRhui:
@@ -512,6 +512,53 @@ func imageTypeFromApiImageType(it ImageTypes, arch distro.Arch) string {
 	return ""
 }
 
+func targetResultToUploadStatus(t *target.TargetResult) (*UploadStatus, error) {
+	var us *UploadStatus
+	var uploadType UploadTypes
+	var uploadOptions interface{}
+
+	switch t.Name {
+	case target.TargetNameAWS:
+		uploadType = UploadTypesAws
+		awsOptions := t.Options.(*target.AWSTargetResultOptions)
+		uploadOptions = AWSEC2UploadStatus{
+			Ami:    awsOptions.Ami,
+			Region: awsOptions.Region,
+		}
+	case target.TargetNameAWSS3:
+		uploadType = UploadTypesAwsS3
+		awsOptions := t.Options.(*target.AWSS3TargetResultOptions)
+		uploadOptions = AWSS3UploadStatus{
+			Url: awsOptions.URL,
+		}
+	case target.TargetNameGCP:
+		uploadType = UploadTypesGcp
+		gcpOptions := t.Options.(*target.GCPTargetResultOptions)
+		uploadOptions = GCPUploadStatus{
+			ImageName: gcpOptions.ImageName,
+			ProjectId: gcpOptions.ProjectID,
+		}
+	case target.TargetNameAzureImage:
+		uploadType = UploadTypesAzure
+		gcpOptions := t.Options.(*target.AzureImageTargetResultOptions)
+		uploadOptions = AzureUploadStatus{
+			ImageName: gcpOptions.ImageName,
+		}
+	default:
+		return nil, fmt.Errorf("unknown upload target: %s", t.Name)
+	}
+
+	us = &UploadStatus{
+		// TODO: determine upload status based on the target results, not job results
+		// Don't set the status here for now, but let it be set by the caller.
+		//Status:  UploadStatusValue(result.UploadStatus),
+		Type:    uploadType,
+		Options: uploadOptions,
+	}
+
+	return us, nil
+}
+
 func (h *apiHandlers) GetComposeStatus(ctx echo.Context, id string) error {
 	return h.server.EnsureJobChannel(h.getComposeStatusImpl)(ctx, id)
 }
@@ -545,47 +592,13 @@ func (h *apiHandlers) getComposeStatusImpl(ctx echo.Context, id string) error {
 			if len(result.TargetResults) != 1 {
 				return HTTPError(ErrorSeveralUploadTargets)
 			}
-			tr := *result.TargetResults[0]
-
-			var uploadType UploadTypes
-			var uploadOptions interface{}
-
-			switch tr.Name {
-			case target.TargetNameAWS:
-				uploadType = UploadTypesAws
-				awsOptions := tr.Options.(*target.AWSTargetResultOptions)
-				uploadOptions = AWSEC2UploadStatus{
-					Ami:    awsOptions.Ami,
-					Region: awsOptions.Region,
-				}
-			case target.TargetNameAWSS3:
-				uploadType = UploadTypesAwsS3
-				awsOptions := tr.Options.(*target.AWSS3TargetResultOptions)
-				uploadOptions = AWSS3UploadStatus{
-					Url: awsOptions.URL,
-				}
-			case target.TargetNameGCP:
-				uploadType = UploadTypesGcp
-				gcpOptions := tr.Options.(*target.GCPTargetResultOptions)
-				uploadOptions = GCPUploadStatus{
-					ImageName: gcpOptions.ImageName,
-					ProjectId: gcpOptions.ProjectID,
-				}
-			case target.TargetNameAzureImage:
-				uploadType = UploadTypesAzure
-				gcpOptions := tr.Options.(*target.AzureImageTargetResultOptions)
-				uploadOptions = AzureUploadStatus{
-					ImageName: gcpOptions.ImageName,
-				}
-			default:
+			tr := result.TargetResults[0]
+			us, err = targetResultToUploadStatus(tr)
+			if err != nil {
 				return HTTPError(ErrorUnknownUploadTarget)
 			}
-
-			us = &UploadStatus{
-				Status:  UploadStatusValue(result.UploadStatus),
-				Type:    uploadType,
-				Options: uploadOptions,
-			}
+			// TODO: determine upload status based on the target results, not job results
+			us.Status = UploadStatusValue(result.UploadStatus)
 		}
 
 		return ctx.JSON(http.StatusOK, ComposeStatus{
@@ -627,10 +640,28 @@ func (h *apiHandlers) getComposeStatusImpl(ctx echo.Context, id string) error {
 			if err != nil {
 				return HTTPError(ErrorGettingBuildDependencyStatus)
 			}
+
+			var us *UploadStatus
+			// only single upload target in addition to Koji is allowed
+			if len(buildJobResult.TargetResults) > 2 {
+				return HTTPError(ErrorSeveralUploadTargets)
+			}
+			for _, tr := range buildJobResult.TargetResults {
+				if tr.Name != target.TargetNameKoji {
+					us, err = targetResultToUploadStatus(tr)
+					if err != nil {
+						return HTTPError(ErrorUnknownUploadTarget)
+					}
+					// TODO: determine upload status based on the target results, not job results
+					us.Status = UploadStatusValue(buildJobResult.UploadStatus)
+				}
+			}
+
 			buildJobResults = append(buildJobResults, buildJobResult)
 			buildJobStatuses = append(buildJobStatuses, ImageStatus{
-				Status: imageStatusFromKojiJobStatus(buildJobStatus, &initResult, &buildJobResult),
-				Error:  composeStatusErrorFromJobError(buildJobError),
+				Status:       imageStatusFromKojiJobStatus(buildJobStatus, &initResult, &buildJobResult),
+				Error:        composeStatusErrorFromJobError(buildJobError),
+				UploadStatus: us,
 			})
 		}
 		response := ComposeStatus{
diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go
index cf4d2b358..4a5e4e65b 100644
--- a/internal/cloudapi/v2/openapi.v2.gen.go
+++ b/internal/cloudapi/v2/openapi.v2.gen.go
@@ -68,6 +68,8 @@ const (
 
 	ImageTypesGcp ImageTypes = "gcp"
 
+	ImageTypesGcpRhui ImageTypes = "gcp-rhui"
+
 	ImageTypesGuestImage ImageTypes = "guest-image"
 
 	ImageTypesImageInstaller ImageTypes = "image-installer"
@@ -712,68 +714,68 @@ var swaggerSpec = []string{
 	"r+WL2fynEcMGuORnqeytVM5+bZbpNhZIF/IIvUXaolJ6KRUO+/ng8REZkcHSQyqPEWQQP5tz0x/IUWrF",
 	"2yfbX3A2C7z9C/WOyt9tx1p75eVN1m1xZYf0H9EuHJIoFJ0bjs7vrMLfL+e3wszQihXHAdjSiANppZ1l",
 	"fillIzUSO+GfAWXB31HRNczr7MnihoRtoIJziQbOeYrZPg7/tOHmLw691c+3gJig/ho+RIaFUqvsdfhL",
-	"+WrEogeYcAEdRz2wdE/+X2rZygyof7dGzbgnw7XYpbTDnOq2bOzbmQtkUAZTdRmKpWqQHwgeHSRfbc3M",
-	"aTlNO9XKaS02IEJshtj2jCjum9IJTpsKcWh70pRZ6rHtj7eKVAzHVlQhn+5av0IuGZMvnCHG97Lj+c9b",
-	"LULy16jC5p81xDVX4uzkqqAUY/Cl+wyznEQVP/YOmkSJSTjyEPhD+q9k/xjuxCUdosP3NsgpJvG5gKiH",
-	"a5/x0YF3/42gAjpxr3a4oJAmV81fQc9VMDl58CyeTIQWf28NHmSIxJxl65DI0wrCKhkIwxoT+CNk3RnQ",
-	"ciWtMM4ZsIROi4WxkS+MK+NKDlbyRVSE5bKRG5c004TfkzKGgmDMINHtlIOnCLAoHbMBj9nIyVQygUfM",
-	"SNX/vnPE2B8Rr5TmftHn82kH24D2ObmTUdpjqR2SsO/T46XlgBjFpe7DzVcY4nZ5tyIXG4rEEoE8euBN",
-	"ZBw/Mn/7dg5brlE89IrAKBQ6EFrGvNgwWZ90SQTRwUG7lAyYsKJRutSNgGbfcECOQunYt9m6QdIMGTYM",
-	"Wkik90JEZAzMRUYKXmUteRIO5RnKM0eYct1G+vTF8qyN9Y4pdRBUyVXLs6Zoua+1l7eXYIqWq5qB5PW6",
-	"DqFSN5ivKxXL7QRUSv5Xa1w2r8Ht5S24va91mnXQbgxBrXNTb6vXIzIi7l3zunZZ1fs6rTWq5x2zMrya",
-	"ordWCRpOdzgvw8vLptOCjqi0JrlFppZrn9hNs+kvLoX3MCmjEen0rPP7cmkCB0Xv4bzoXnRbeW+KCOpl",
-	"9IH7+no3vV7ecfspR++e5o23+/44W7/u1s36pTV9qtzlRuTtecqaep1daHe5OWuPHegb9v0JfoCkes7d",
-	"bGXYeOXjYvU+XzbEPevm74bGo3XaO3nCt+ZDpTci7dpkoOVnD7Ubo9vnw/xpB9ZJqellb2ZepdmgmSZq",
-	"PAyzr2795rYK29q4dZX3TatQ99GUnwz6IzK/exygemfhP3dKN90nenPbns+6d+ZibGWfzisz/1lri0lG",
-	"v77KLaCvLVxe9U+vWh6azm5uewtnRJavYrJ8Nhl9wOhi6c2frdndXBDSrWSsfsPPtB4GbKgVc27jflCu",
-	"6+NyYapfXQwuzO7UIdPLzIho5n2h2oNFrXCVX0y0qRij/Kyt3z7R2xu/XXvgV/2Zpt1fDqvLW+QvTypl",
-	"/T4zbNjd8jTff2hPRqSEms/WEndvtLmTHV6e99q678yn/LR64jtTK0sH4wLPv7nPs1utfEkHi8dCbgLb",
-	"xcf+ybX9jNCIVEraE32wx3q27fVPJuYznXDWEM+V2/H988lwdlHpecx4rLLJ1bg1zbW8Xru6GNgLflfl",
-	"NfsyOyJax1/kHmG3plm5ZvFW7xqtjP46oVpF19mk9uTjxSPDReyfdp+8yusgY/bfrl1uNC1Sybw+t0cE",
-	"V+58x/TLZf/VfszMRW4sCBZWj79O7EXXnwzvC8/jgj0VFxW7fZ95eioXcq92p9ieV3vVu2ptRMT5xeXz",
-	"Y2+muw2rfd7NtvvVyrP7MB3nW3Zn0M12nmpL+Ji1deJUo+f6VWsG3YeJUS/ORkR39RN817qp1bq1erVa",
-	"uMCNBroqucy+uCr7D/yu0+3mtGFRf7bJYli5qLpKh+qX88pFfT5tjkht3ry8uKOtepXXa7VhvTpv1K+s",
-	"Rv2iUK3WrendevbJ9bCaKdeGnuUs+9Xn4ZU9WbbtEcmcmKW3W/NhNr7KaY3X/LRZvrmoXWuk83RSu8+6",
-	"/qx/8jrw+/nHDqvl3fyl7wiv3Wu02h3hFhvnI5Jll29PVTrILr3TYbPSqZ4b3Xr9ZjmpTjh9vK+Uh/d+",
-	"/SQzJhM2QL1cp3dTN5e39XLp8bRSxDcPI+IW+ydjfnc+L9dzHeYY1W6he+7T5XO2j8UlfC607zoP4mTQ",
-	"gNkC5sP+ZX3yRsu3w8pDvnUzLWojYr0+WpXcdWbs5hpv/fKgkn9snI+zzmxSaDqzhdV8bSMrm317Gi5c",
-	"Nuw/t1p1c/ZmnjjX/ZK/sK5GZLLItLSl85zr4PElK11Wq8ub0/tHVn3uz/tdraFPBpV5o04W0/65v3x1",
-	"H+cPs+vak99oPlRuUH44Il18nzVb1xVulM89frEodk+eDNIld/2TKzYZ3LbP8+4jc6oGaQxsY/hQmTxP",
-	"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",
-	"o5hIonvSKzSfwfnx/n8DAAD//z1YbXMTWwAA",
+	"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",
 }
 
 // GetSwagger returns the content of the embedded swagger specification file
diff --git a/internal/cloudapi/v2/openapi.v2.yml b/internal/cloudapi/v2/openapi.v2.yml
index 7b9a59060..3c6d58997 100644
--- a/internal/cloudapi/v2/openapi.v2.yml
+++ b/internal/cloudapi/v2/openapi.v2.yml
@@ -655,6 +655,7 @@ components:
         - edge-container
         - edge-installer
         - gcp
+        - gcp-rhui
         - guest-image
         - image-installer
         - vsphere
diff --git a/internal/cloudapi/v2/server.go b/internal/cloudapi/v2/server.go
index b2ddba870..4d7a6671e 100644
--- a/internal/cloudapi/v2/server.go
+++ b/internal/cloudapi/v2/server.go
@@ -185,12 +185,18 @@ func (s *Server) enqueueKojiCompose(taskID uint64, server, name, version, releas
 		kojiTarget.OsbuildArtifact.ExportName = ir.imageType.Exports()[0]
 		kojiTarget.ImageName = kojiFilename
 
+		targets := []*target.Target{kojiTarget}
+		// add any cloud upload target if defined
+		if ir.target != nil {
+			targets = append(targets, ir.target)
+		}
+
 		buildID, err := s.workers.EnqueueOSBuildAsDependency(ir.arch.Name(), &worker.OSBuildJob{
 			PipelineNames: &worker.PipelineNames{
 				Build:   ir.imageType.BuildPipelines(),
 				Payload: ir.imageType.PayloadPipelines(),
 			},
-			Targets:            []*target.Target{kojiTarget},
+			Targets:            targets,
 			ManifestDynArgsIdx: common.IntToPtr(1),
 		}, []uuid.UUID{initID, manifestJobID}, channel)
 		if err != nil {
diff --git a/internal/worker/clienterrors/errors.go b/internal/worker/clienterrors/errors.go
index b8587f004..54290f714 100644
--- a/internal/worker/clienterrors/errors.go
+++ b/internal/worker/clienterrors/errors.go
@@ -29,6 +29,7 @@ const (
 	ErrorJobDependency        ClientErrorCode = 26
 	ErrorJobMissingHeartbeat  ClientErrorCode = 27
 	ErrorTargetError          ClientErrorCode = 28
+	ErrorParsingJobArgs       ClientErrorCode = 29
 )
 
 type ClientErrorCode int
diff --git a/internal/worker/json.go b/internal/worker/json.go
index bcb1a951c..817665e7b 100644
--- a/internal/worker/json.go
+++ b/internal/worker/json.go
@@ -75,6 +75,20 @@ func (j *OSBuildJobResult) TargetErrors() []*clienterrors.Error {
 	return targetErrors
 }
 
+// TargetResultsByName iterates over TargetResults attached to the Job result and
+// returns a slice of Target results of the provided name (type). If there were no
+// TargetResults of the desired type attached to the Job results, the returned
+// slice will be empty.
+func (j *OSBuildJobResult) TargetResultsByName(name target.TargetName) []*target.TargetResult {
+	targetResults := []*target.TargetResult{}
+	for _, targetResult := range j.TargetResults {
+		if targetResult.Name == name {
+			targetResults = append(targetResults, targetResult)
+		}
+	}
+	return targetResults
+}
+
 type KojiInitJob struct {
 	Server  string `json:"server"`
 	Name    string `json:"name"`
diff --git a/tools/koji-compose.py b/tools/koji-compose.py
index bbde97e5a..8f95d9f6c 100755
--- a/tools/koji-compose.py
+++ b/tools/koji-compose.py
@@ -2,6 +2,7 @@
 import json
 import sys
 import time
+import os
 
 import requests
 
@@ -28,12 +29,12 @@ def compose_request(distro, koji, arch):
     repositories = [composer_repository_to_koji_repository(repo) for repo in test_repositories[arch]]
     image_requests = [
         {
-            "architecture": "x86_64",
+            "architecture": arch,
             "image_type": "guest-image",
             "repositories": repositories
         },
         {
-            "architecture": "x86_64",
+            "architecture": arch,
             "image_type": "aws",
             "repositories": repositories
         }
@@ -54,42 +55,112 @@ def compose_request(distro, koji, arch):
     return req
 
 
-def main(distro, arch):
-    cr = compose_request(distro, "https://localhost:4343/kojihub", arch)
-    print(json.dumps(cr))
+def upload_options_by_cloud_target(cloud_target):
+    if cloud_target == "aws":
+        return {
+            # the snapshot name is currently not set for Koji composes
+            # "snapshot_name": "",
+            "region": os.getenv("AWS_REGION"),
+            "share_with_accounts": [os.getenv("AWS_API_TEST_SHARE_ACCOUNT")]
+        }
+    elif cloud_target == "azure":
+        return {
+            # image name is currently not set for Koji composes
+            # "image_name": "",
+            "location": os.getenv("AZURE_LOCATION"),
+            "resource_group": os.getenv("AZURE_RESOURCE_GROUP"),
+            "subscription_id": os.getenv("AZURE_SUBSCRIPTION_ID"),
+            "tenant_id": os.getenv("AZURE_TENANT_ID")
+        }
+    elif cloud_target == "gcp":
+        return {
+            # image name is currently not set for Koji composes
+            # "image_name": "",
+            "bucket": os.getenv("GCP_BUCKET"),
+            "region": os.getenv("GCP_REGION"),
+            "share_with_accounts": [os.getenv("GCP_API_TEST_SHARE_ACCOUNT")]
+        }
+    else:
+        raise RuntimeError(f"unsupported target cloud: {cloud_target}")
+
+
+def compose_request_cloud_upload(distro, koji, arch, cloud_target, image_type):
+    with open(f"/usr/share/tests/osbuild-composer/repositories/{distro}.json") as f:
+        test_repositories = json.load(f)
+
+    repositories = [composer_repository_to_koji_repository(repo) for repo in test_repositories[arch]]
+    image_requests = [
+        {
+            "architecture": arch,
+            "image_type": image_type,
+            "repositories": repositories,
+            "upload_options": upload_options_by_cloud_target(cloud_target)
+        },
+    ]
+
+    req = {
+        "distribution": distro,
+        "koji": {
+            "server": koji,
+            "task_id": 1,
+            "name": "name",
+            "version": "version",
+            "release": "release",
+        },
+        "image_requests": image_requests
+    }
+
+    return req
+
+
+def main():
+    koji_url = "https://localhost:4343/kojihub"
+    if len(sys.argv) == 3:
+        distro, arch = sys.argv[1], sys.argv[2]
+        print("Using simple Koji compose with 2 image requests", file=sys.stderr)
+        cr = compose_request(distro, koji_url, arch)
+    elif len(sys.argv) == 5:
+        distro, arch, cloud_target, image_type = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
+        print("Using Koji compose with 1 image requests and upload to cloud", file=sys.stderr)
+        cr = compose_request_cloud_upload(distro, koji_url, arch, cloud_target, image_type)
+    else:
+        print(f"usage: {sys.argv[0]} DISTRO ARCH [CLOUD_TARGET IMAGE_TYPE]", file=sys.stderr)
+        sys.exit(1)
+    print(json.dumps(cr), file=sys.stderr)
 
     r = requests.post("https://localhost/api/image-builder-composer/v2/compose", json=cr,
                       cert=("/etc/osbuild-composer/worker-crt.pem", "/etc/osbuild-composer/worker-key.pem"),
                       verify="/etc/osbuild-composer/ca-crt.pem")
     if r.status_code != 201:
-        print("Failed to create compose")
-        print(r.text)
+        print("Failed to create compose", file=sys.stderr)
+        print(r.text, file=sys.stderr)
         sys.exit(1)
 
-    print(r.text)
+    print(r.text, file=sys.stderr)
     compose_id = r.json()["id"]
+    print(compose_id)
 
     while True:
         r = requests.get(f"https://localhost/api/image-builder-composer/v2/composes/{compose_id}",
                          cert=("/etc/osbuild-composer/worker-crt.pem", "/etc/osbuild-composer/worker-key.pem"),
                          verify="/etc/osbuild-composer/ca-crt.pem")
         if r.status_code != 200:
-            print("Failed to get compose status")
-            print(r.text)
+            print("Failed to get compose status", file=sys.stderr)
+            print(r.text, file=sys.stderr)
             sys.exit(1)
         status = r.json()["status"]
-        print(status)
+        print(status, file=sys.stderr)
         if status == "success":
-            print("Compose worked!")
-            print(r.text)
+            print("Compose worked!", file=sys.stderr)
+            print(r.text, file=sys.stderr)
             break
         elif status == "failure":
-            print("compose failed!")
-            print(r.text)
+            print("compose failed!", file=sys.stderr)
+            print(r.text, file=sys.stderr)
             sys.exit(1)
         elif status != "pending" and status != "running":
-            print(f"unexpected status: {status}")
-            print(r.text)
+            print(f"unexpected status: {status}", file=sys.stderr)
+            print(r.text, file=sys.stderr)
             sys.exit(1)
 
         time.sleep(10)
@@ -104,7 +175,4 @@ def main(distro, arch):
 
 
 if __name__ == "__main__":
-    if len(sys.argv) != 3:
-        print(f"usage: {sys.argv[0]} DISTRO ARCH", file=sys.stderr)
-        sys.exit(1)
-    main(sys.argv[1], sys.argv[2])
+    main()


Test patch:
diff --git a/internal/worker/json_test.go b/internal/worker/json_test.go
index 6f629e27f..f3565f44f 100644
--- a/internal/worker/json_test.go
+++ b/internal/worker/json_test.go
@@ -87,6 +87,108 @@ func TestOSBuildJobResultTargetErrors(t *testing.T) {
 	}
 }
 
+func TestOSBuildJobResultTargetResultsByName(t *testing.T) {
+	testCases := []struct {
+		jobResult     OSBuildJobResult
+		targetName    target.TargetName
+		targetResults []*target.TargetResult
+	}{
+		// one target results of a given name
+		{
+			jobResult: OSBuildJobResult{
+				TargetResults: []*target.TargetResult{
+					{
+						Name:        target.TargetNameAWS,
+						TargetError: clienterrors.WorkerClientError(clienterrors.ErrorInvalidTargetConfig, "can't login to AWS"),
+					},
+					{
+						Name:        target.TargetNameVMWare,
+						TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
+					},
+					{
+						Name:        target.TargetNameVMWare,
+						TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
+					},
+					{
+						Name:        target.TargetNameAWSS3,
+						TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "failed to upload image to AWS S3"),
+					},
+				},
+			},
+			targetName: target.TargetNameAWS,
+			targetResults: []*target.TargetResult{
+				{
+					Name:        target.TargetNameAWS,
+					TargetError: clienterrors.WorkerClientError(clienterrors.ErrorInvalidTargetConfig, "can't login to AWS"),
+				},
+			},
+		},
+		// multiple target results of a given name
+		{
+			jobResult: OSBuildJobResult{
+				TargetResults: []*target.TargetResult{
+					{
+						Name:        target.TargetNameAWS,
+						TargetError: clienterrors.WorkerClientError(clienterrors.ErrorInvalidTargetConfig, "can't login to AWS"),
+					},
+					{
+						Name:        target.TargetNameVMWare,
+						TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
+					},
+					{
+						Name:        target.TargetNameVMWare,
+						TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
+					},
+					{
+						Name:        target.TargetNameAWSS3,
+						TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "failed to upload image to AWS S3"),
+					},
+				},
+			},
+			targetName: target.TargetNameVMWare,
+			targetResults: []*target.TargetResult{
+				{
+					Name:        target.TargetNameVMWare,
+					TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
+				},
+				{
+					Name:        target.TargetNameVMWare,
+					TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
+				},
+			},
+		},
+		// no target result of a given name
+		{
+			jobResult: OSBuildJobResult{
+				TargetResults: []*target.TargetResult{
+					{
+						Name:        target.TargetNameAWS,
+						TargetError: clienterrors.WorkerClientError(clienterrors.ErrorInvalidTargetConfig, "can't login to AWS"),
+					},
+					{
+						Name:        target.TargetNameVMWare,
+						TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
+					},
+					{
+						Name:        target.TargetNameVMWare,
+						TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "can't upload image to VMWare"),
+					},
+					{
+						Name:        target.TargetNameAWSS3,
+						TargetError: clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, "failed to upload image to AWS S3"),
+					},
+				},
+			},
+			targetName:    target.TargetNameKoji,
+			targetResults: []*target.TargetResult{},
+		},
+	}
+
+	for _, testCase := range testCases {
+		assert.EqualValues(t, testCase.targetResults, testCase.jobResult.TargetResultsByName(testCase.targetName))
+	}
+}
+
 func TestOSBuildJobExports(t *testing.T) {
 	testCases := []struct {
 		job             *OSBuildJob
diff --git a/test/cases/api/azure.sh b/test/cases/api/azure.sh
index 76be691b8..263cc253d 100644
--- a/test/cases/api/azure.sh
+++ b/test/cases/api/azure.sh
@@ -109,11 +109,16 @@ function checkUploadStatusOptions() {
   test "$IMAGE_NAME" = "$AZURE_IMAGE_NAME"
 }
 
-# Verify image in Azure
-function verify() {
+# Log into Azure
+function cloud_login() {
   set +x
   $AZURE_CMD login --service-principal --username "${V2_AZURE_CLIENT_ID}" --password "${V2_AZURE_CLIENT_SECRET}" --tenant "${AZURE_TENANT_ID}"
   set -x
+}
+
+# Verify image in Azure
+function verify() {
+  cloud_login
 
   # verify that the image exists and tag it
   $AZURE_CMD image show --resource-group "${AZURE_RESOURCE_GROUP}" --name "${AZURE_IMAGE_NAME}"
diff --git a/test/cases/api/common/aws.sh b/test/cases/api/common/aws.sh
index 14c315e54..7037056b3 100644
--- a/test/cases/api/common/aws.sh
+++ b/test/cases/api/common/aws.sh
@@ -31,3 +31,10 @@ function installClient() {
   fi
   $GOVC_CMD version
 }
+
+# Log into AWS
+# AWS does not need explicit login, but define this function for the sake of
+# consistency to allow calling scripts to not care about cloud differences
+function cloud_login() {
+  true
+}
diff --git a/test/cases/api/gcp.sh b/test/cases/api/gcp.sh
index 267b8159c..d88877c8a 100644
--- a/test/cases/api/gcp.sh
+++ b/test/cases/api/gcp.sh
@@ -101,14 +101,18 @@ function checkUploadStatusOptions() {
   test "$PROJECT_ID" = "$GCP_PROJECT"
 }
 
-
-# Verify image in Compute Engine on GCP
-function verify() {
+# Log into GCP
+function cloud_login() {
   # Authenticate
   $GCP_CMD auth activate-service-account --key-file "$GOOGLE_APPLICATION_CREDENTIALS"
   # Extract and set the default project to be used for commands
   GCP_PROJECT=$(jq -r '.project_id' "$GOOGLE_APPLICATION_CREDENTIALS")
   $GCP_CMD config set project "$GCP_PROJECT"
+}
+
+# Verify image in Compute Engine on GCP
+function verify() {
+  cloud_login
 
   # Add "gitlab-ci-test" label to the image
   $GCP_CMD compute images add-labels "$GCP_IMAGE_NAME" --labels=gitlab-ci-test=true
diff --git a/test/cases/koji.sh b/test/cases/koji.sh
index 887fe73c4..1c3fa1049 100755
--- a/test/cases/koji.sh
+++ b/test/cases/koji.sh
@@ -16,6 +16,89 @@ if [ "${NIGHTLY:=false}" == "true" ]; then
     exit 0
 fi
 
+#
+# Cloud provider / target names
+#
+
+CLOUD_PROVIDER_AWS="aws"
+CLOUD_PROVIDER_GCP="gcp"
+CLOUD_PROVIDER_AZURE="azure"
+
+#
+# Test types
+#
+# Tests Koji compose via cloudapi
+TEST_TYPE_CLOUDAPI="cloudapi"
+# Tests Koji compose via cloudapi with upload to a cloud target
+TEST_TYPE_CLOUD_UPLOAD="cloud-upload"
+
+# test Koji compose via cloudapi without upload to cloud by default
+TEST_TYPE="${1:-$TEST_TYPE_CLOUDAPI}"
+
+#
+# Cloud upload - check environment and prepare it
+#
+if [[ "$TEST_TYPE" == "$TEST_TYPE_CLOUD_UPLOAD" ]]; then
+    if [[ $# -ne 3 ]]; then
+        echo "Usage: $0 cloud-upload TARGET IMAGE_TYPE"
+        exit 1
+    fi
+
+    CLOUD_PROVIDER="${2}"
+    IMAGE_TYPE="${3}"
+
+    greenprint "Using Cloud Provider / Target ${CLOUD_PROVIDER} for Image Type ${IMAGE_TYPE}"
+
+    # Load a correct test runner.
+    # Each one must define following methods:
+    # - checkEnv
+    # - cleanup
+    # - installClient
+    case $CLOUD_PROVIDER in
+        "$CLOUD_PROVIDER_AWS")
+            source /usr/libexec/tests/osbuild-composer/api/aws.sh
+            ;;
+        "$CLOUD_PROVIDER_GCP")
+            source /usr/libexec/tests/osbuild-composer/api/gcp.sh
+            ;;
+        "$CLOUD_PROVIDER_AZURE")
+            source /usr/libexec/tests/osbuild-composer/api/azure.sh
+            ;;
+        *)
+            echo "Unknown cloud provider: ${CLOUD_PROVIDER}"
+            exit 1
+    esac
+
+    # Verify that this script is running in the right environment.
+    checkEnv
+
+    # Container image used for cloud provider CLI tools
+    export CONTAINER_IMAGE_CLOUD_TOOLS="quay.io/osbuild/cloud-tools:latest"
+
+    if which podman 2>/dev/null >&2; then
+        export CONTAINER_RUNTIME=podman
+    elif which docker 2>/dev/null >&2; then
+        export CONTAINER_RUNTIME=docker
+    else
+        echo No container runtime found, install podman or docker.
+        exit 1
+    fi
+
+    WORKDIR=$(mktemp -d)
+    export WORKDIR
+    function cleanups() {
+        greenprint "Script execution stopped or finished - Cleaning up"
+        set +eu
+        cleanup
+        sudo rm -rf "$WORKDIR"
+        set -eu
+    }
+    trap cleanups EXIT
+
+    # install appropriate cloud environment client tool
+    installClient
+fi
+
 # Provision the software under test.
 /usr/libexec/osbuild-composer-test/provision.sh
 
@@ -51,13 +134,73 @@ koji --server=http://localhost:8080/kojihub --user kojiadmin --password kojipass
 
 # Always build the latest RHEL - that suits the koji API usecase the most.
 if [[ "$DISTRO_CODE" == rhel-8* ]]; then
-  DISTRO_CODE=rhel-87
+    DISTRO_CODE=rhel-87
 elif [[ "$DISTRO_CODE" == rhel-9* ]]; then
-  DISTRO_CODE=rhel-91
+    DISTRO_CODE=rhel-91
 fi
 
-greenprint "Pushing compose to Koji (/api/image-builder-comoser/v2/"
-sudo /usr/libexec/osbuild-composer-test/koji-compose.py "$DISTRO_CODE" "${ARCH}"
+case ${TEST_TYPE} in
+    "$TEST_TYPE_CLOUDAPI")
+        greenprint "Pushing compose to Koji (/api/image-builder-comoser/v2/)"
+        COMPOSE_ID="$(sudo /usr/libexec/osbuild-composer-test/koji-compose.py "${DISTRO_CODE}" "${ARCH}")"
+        ;;
+    "$TEST_TYPE_CLOUD_UPLOAD")
+        greenprint "Pushing compose to Koji (/api/image-builder-comoser/v2/) with cloud upload target"
+        COMPOSE_ID="$(sudo -E /usr/libexec/osbuild-composer-test/koji-compose.py "${DISTRO_CODE}" "${ARCH}" "${CLOUD_PROVIDER}" "${IMAGE_TYPE}")"
+        ;;
+    *)
+        echo "Unknown test type: ${TEST_TYPE}"
+        exit 1
+esac
+
+if [[ "$TEST_TYPE" == "$TEST_TYPE_CLOUD_UPLOAD" ]]; then
+    greenprint "Verify that image was uploaded to the cloud provider"
+
+    COMPOSE_STATUS=$(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}")
+    UPLOAD_OPTIONS=$(echo "${COMPOSE_STATUS}" | jq -r '.image_status.upload_status.options')
+
+    # Authenticate with the appropriate cloud
+    cloud_login
+
+    case $CLOUD_PROVIDER in
+        "$CLOUD_PROVIDER_AWS")
+            AMI_IMAGE_ID=$(echo "${UPLOAD_OPTIONS}" | jq -r '.ami')
+            $AWS_CMD ec2 describe-images \
+                --owners self \
+                --filters Name=image-id,Values="${AMI_IMAGE_ID}" \
+                > "${WORKDIR}/ami.json"
+            # extract the snapshot ID for the purpose of cleanup
+            AWS_SNAPSHOT_ID=$(jq -r '.Images[].BlockDeviceMappings[].Ebs.SnapshotId' "$WORKDIR/ami.json")
+            export AWS_SNAPSHOT_ID
+            if [[ $(jq '.Images | length' "${WORKDIR}/ami.json") -ne 1 ]]; then
+                echo "${AMI_IMAGE_ID} image not found in AWS"
+                exit 1
+            fi
+            ;;
+        "$CLOUD_PROVIDER_GCP")
+            GCP_IMAGE_NAME=$(echo "${UPLOAD_OPTIONS}" | jq -r '.image_name')
+            # The command exits with non-zero value if the image does not exist
+            $GCP_CMD compute images describe "${GCP_IMAGE_NAME}"
+            ;;
+        "$CLOUD_PROVIDER_AZURE")
+            AZURE_IMAGE_NAME=$(echo "${UPLOAD_OPTIONS}" | jq -r '.image_name')
+            # The command exits with non-zero value if the image does not exist
+            $AZURE_CMD image show --resource-group "${AZURE_RESOURCE_GROUP}" --name "${AZURE_IMAGE_NAME}"
+            ;;
+        *)
+            echo "Unknown cloud provider: ${CLOUD_PROVIDER}"
+            exit 1
+    esac
+
+    # if we got here, the image must have been found
+    greenprint "Image was SUCCESSFULLY found in the respective cloud provider environment!"
+fi
 
 greenprint "Show Koji task"
 koji --server=http://localhost:8080/kojihub taskinfo 1
diff --git a/test/data/repositories/rhel-86.json b/test/data/repositories/rhel-86.json
index c79389918..10b07a411 100644
--- a/test/data/repositories/rhel-86.json
+++ b/test/data/repositories/rhel-86.json
@@ -62,6 +62,24 @@
       "rhsm": true,
       "check_gpg": true
     },
+    {
+      "name": "rhui",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-x86_64-rhui-3-20220201",
+      "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\niA==\n=+Gxh\n-----END PGP PUBLIC KEY BLOCK-----\n",
+      "image_type_tags": [
+        "ec2",
+        "ec2-ha",
+        "ec2-sap"
+      ]
+    },
+    {
+      "name": "rhui-azure",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-x86_64-rhui-azure-20220227",
+      "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\niA==\n=+Gxh\n-----END PGP PUBLIC KEY BLOCK-----\n",
+      "image_type_tags": [
+        "azure-rhui"
+      ]
+    },
     {
       "name": "google-compute-engine",
       "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/el8-x86_64-google-compute-engine-20220227",
diff --git a/test/data/repositories/rhel-87.json b/test/data/repositories/rhel-87.json
index f2b4496a9..7f8501f4e 100644
--- a/test/data/repositories/rhel-87.json
+++ b/test/data/repositories/rhel-87.json
@@ -15,6 +15,24 @@
       "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-x86_64-rt-n8.7-20220415/",
       "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\niA==\n=+Gxh\n-----END PGP PUBLIC KEY BLOCK-----\n"
     },
+    {
+      "name": "rhui",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-x86_64-rhui-3-20220201",
+      "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\niA==\n=+Gxh\n-----END PGP PUBLIC KEY BLOCK-----\n",
+      "image_type_tags": [
+        "ec2",
+        "ec2-ha",
+        "ec2-sap"
+      ]
+    },
+    {
+      "name": "rhui-azure",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el8/el8-x86_64-rhui-azure-20220227",
+      "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\niA==\n=+Gxh\n-----END PGP PUBLIC KEY BLOCK-----\n",
+      "image_type_tags": [
+        "azure-rhui"
+      ]
+    },
     {
       "name": "google-compute-engine",
       "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el8/el8-x86_64-google-compute-engine-20220227",
diff --git a/test/data/repositories/rhel-90.json b/test/data/repositories/rhel-90.json
index 14dec2e5e..a7a81819b 100644
--- a/test/data/repositories/rhel-90.json
+++ b/test/data/repositories/rhel-90.json
@@ -69,6 +69,24 @@
       "rhsm": true,
       "check_gpg": true
     },
+    {
+      "name": "rhui",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-rhui-4-20220208/",
+      "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\nmQINBGIpIp4BEAC/o5e1WzLIsS6/JOQCs4XYATYTcf6B6ALzcP05G0W3uRpUQSrL\nFRKNrU8ZCelm/B+XSh2ljJNeklp2WLxYENDOsftDXGoyLr2hEkI5OyK267IHhFNJ\ng+BN+T5Cjh4ZiiWij6o9F7x2ZpxISE9M4iI80rwSv1KOnGSw5j2zD2EwoMjTVyVE\n/t3s5XJxnDclB7ZqL+cgjv0mWUY/4+b/OoRTkhq7b8QILuZp75Y64pkrndgakm1T\n8mAGXV02mEzpNj9DyAJdUqa11PIhMJMxxHOGHJ8CcHZ2NJL2e7yJf4orTj+cMhP5\nLzJcVlaXnQYu8Zkqa0V6J1Qdj8ZXL72QsmyicRYXAtK9Jm5pvBHuYU2m6Ja7dBEB\nVkhe7lTKhAjkZC5ErPmANNS9kPdtXCOpwN1lOnmD2m04hks3kpH9OTX7RkTFUSws\neARAfRID6RLfi59B9lmAbekecnsMIFMx7qR7ZKyQb3GOuZwNYOaYFevuxusSwCHv\n4FtLDIhk+Fge+EbPdEva+VLJeMOb02gC4V/cX/oFoPkxM1A5LHjkuAM+aFLAiIRd\nNp/tAPWk1k6yc+FqkcDqOttbP4ciiXb9JPtmzTCbJD8lgH0rGp8ufyMXC9x7/dqX\nTjsiGzyvlMnrkKB4GL4DqRFl8LAR02A3846DD8CAcaxoXggL2bJCU2rgUQARAQAB\ntDVSZWQgSGF0LCBJbmMuIChhdXhpbGlhcnkga2V5IDMpIDxzZWN1cml0eUByZWRo\nYXQuY29tPokCUgQTAQgAPBYhBH5GJCWMQGU11W1vE1BU5KRaY0CzBQJiKSKeAhsD\nBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeBwIXgAAKCRBQVOSkWmNAsyBfEACuTN/X\nYR+QyzeRw0pXcTvMqzNE4DKKr97hSQEwZH1/v1PEPs5O3psuVUm2iam7bqYwG+ry\nEskAgMHi8AJmY0lioQD5/LTSLTrM8UyQnU3g17DHau1NHIFTGyaW4a7xviU4C2+k\nc6X0u1CPHI1U4Q8prpNcfLsldaNYlsVZtUtYSHKPAUcswXWliW7QYjZ5tMSbu8jR\nOMOc3mZuf0fcVFNu8+XSpN7qLhRNcPv+FCNmk/wkaQfH4Pv+jVsOgHqkV3aLqJeN\nkNUnpyEKYkNqo7mNfNVWOcl+Z1KKKwSkIi3vg8maC7rODsy6IX+Y96M93sqYDQom\naaWue2gvw6thEoH4SaCrCL78mj2YFpeg1Oew4QwVcBnt68KOPfL9YyoOicNs4Vuu\nfb/vjU2ONPZAeepIKA8QxCETiryCcP43daqThvIgdbUIiWne3gae6eSj0EuUPoYe\nH5g2Lw0qdwbHIOxqp2kvN96Ii7s1DK3VyhMt/GSPCxRnDRJ8oQKJ2W/I1IT5VtiU\nzMjjq5JcYzRPzHDxfVzT9CLeU/0XQ+2OOUAiZKZ0dzSyyVn8xbpviT7iadvjlQX3\nCINaPB+d2Kxa6uFWh+ZYOLLAgZ9B8NKutUHpXN66YSfe79xFBSFWKkJ8cSIMk13/\nIfs7ApKlKCCRDpwoDqx/sjIaj1cpOfLHYjnefg==\n=UZd/\n-----END PGP PUBLIC KEY BLOCK-----\n",
+      "image_type_tags": [
+        "ec2",
+        "ec2-ha",
+        "ec2-sap"
+      ]
+    },
+    {
+      "name": "rhui-azure",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-rhui-azure-20220518",
+      "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\niA==\n=+Gxh\n-----END PGP PUBLIC KEY BLOCK-----\n",
+      "image_type_tags": [
+        "azure-rhui"
+      ]
+    },
     {
       "name": "google-compute-engine",
       "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/el9-x86_64-google-compute-engine-20220322",
diff --git a/test/data/repositories/rhel-91.json b/test/data/repositories/rhel-91.json
index e165650ad..7ced996b9 100644
--- a/test/data/repositories/rhel-91.json
+++ b/test/data/repositories/rhel-91.json
@@ -51,6 +51,25 @@
       "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-rt-n9.1-20220415/",
       "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\nmQINBGIpIp4BEAC/o5e1WzLIsS6/JOQCs4XYATYTcf6B6ALzcP05G0W3uRpUQSrL\nFRKNrU8ZCelm/B+XSh2ljJNeklp2WLxYENDOsftDXGoyLr2hEkI5OyK267IHhFNJ\ng+BN+T5Cjh4ZiiWij6o9F7x2ZpxISE9M4iI80rwSv1KOnGSw5j2zD2EwoMjTVyVE\n/t3s5XJxnDclB7ZqL+cgjv0mWUY/4+b/OoRTkhq7b8QILuZp75Y64pkrndgakm1T\n8mAGXV02mEzpNj9DyAJdUqa11PIhMJMxxHOGHJ8CcHZ2NJL2e7yJf4orTj+cMhP5\nLzJcVlaXnQYu8Zkqa0V6J1Qdj8ZXL72QsmyicRYXAtK9Jm5pvBHuYU2m6Ja7dBEB\nVkhe7lTKhAjkZC5ErPmANNS9kPdtXCOpwN1lOnmD2m04hks3kpH9OTX7RkTFUSws\neARAfRID6RLfi59B9lmAbekecnsMIFMx7qR7ZKyQb3GOuZwNYOaYFevuxusSwCHv\n4FtLDIhk+Fge+EbPdEva+VLJeMOb02gC4V/cX/oFoPkxM1A5LHjkuAM+aFLAiIRd\nNp/tAPWk1k6yc+FqkcDqOttbP4ciiXb9JPtmzTCbJD8lgH0rGp8ufyMXC9x7/dqX\nTjsiGzyvlMnrkKB4GL4DqRFl8LAR02A3846DD8CAcaxoXggL2bJCU2rgUQARAQAB\ntDVSZWQgSGF0LCBJbmMuIChhdXhpbGlhcnkga2V5IDMpIDxzZWN1cml0eUByZWRo\nYXQuY29tPokCUgQTAQgAPBYhBH5GJCWMQGU11W1vE1BU5KRaY0CzBQJiKSKeAhsD\nBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeBwIXgAAKCRBQVOSkWmNAsyBfEACuTN/X\nYR+QyzeRw0pXcTvMqzNE4DKKr97hSQEwZH1/v1PEPs5O3psuVUm2iam7bqYwG+ry\nEskAgMHi8AJmY0lioQD5/LTSLTrM8UyQnU3g17DHau1NHIFTGyaW4a7xviU4C2+k\nc6X0u1CPHI1U4Q8prpNcfLsldaNYlsVZtUtYSHKPAUcswXWliW7QYjZ5tMSbu8jR\nOMOc3mZuf0fcVFNu8+XSpN7qLhRNcPv+FCNmk/wkaQfH4Pv+jVsOgHqkV3aLqJeN\nkNUnpyEKYkNqo7mNfNVWOcl+Z1KKKwSkIi3vg8maC7rODsy6IX+Y96M93sqYDQom\naaWue2gvw6thEoH4SaCrCL78mj2YFpeg1Oew4QwVcBnt68KOPfL9YyoOicNs4Vuu\nfb/vjU2ONPZAeepIKA8QxCETiryCcP43daqThvIgdbUIiWne3gae6eSj0EuUPoYe\nH5g2Lw0qdwbHIOxqp2kvN96Ii7s1DK3VyhMt/GSPCxRnDRJ8oQKJ2W/I1IT5VtiU\nzMjjq5JcYzRPzHDxfVzT9CLeU/0XQ+2OOUAiZKZ0dzSyyVn8xbpviT7iadvjlQX3\nCINaPB+d2Kxa6uFWh+ZYOLLAgZ9B8NKutUHpXN66YSfe79xFBSFWKkJ8cSIMk13/\nIfs7ApKlKCCRDpwoDqx/sjIaj1cpOfLHYjnefg==\n=UZd/\n-----END PGP PUBLIC KEY BLOCK-----\n"
     },
+    {
+      "name": "rhui",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-rhui-4-20220324/",
+      "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\niA==\n=+Gxh\n-----END PGP PUBLIC KEY BLOCK-----\n",
+      "check_gpg": true,
+      "image_type_tags": [
+        "ec2",
+        "ec2-ha",
+        "ec2-sap"
+      ]
+    },
+    {
+      "name": "rhui-azure",
+      "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/rhvpn/el9/el9-x86_64-rhui-azure-20220518",
+      "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\niA==\n=+Gxh\n-----END PGP PUBLIC KEY BLOCK-----\n",
+      "image_type_tags": [
+        "azure-rhui"
+      ]
+    },
     {
       "name": "google-compute-engine",
       "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/el9/el9-x86_64-google-compute-engine-20220322",