Repository: osbuild/osbuild-composer
Pull request #186 (2019-12-28 01:40:20)

integration tests: add subpackage with integration tests
The overall idea is to ship integration tests as a subpackage to be installed and run on the target machine, not requiring any additional dependencies or framework to be installed on the target, and allowing the test suite to be triggered form the outside.

See individual commits for details.

ondrejbudai: `673ad7d6` - Imho we should merge this asap (also it's kinda unrelated), can you cherrypick it on a different branch.
msehnout: I like this PR.

One question though: I'd like to see the tests as part of our CI, do you have any plan for this?
teg: @msehnout yeah, the aim is to get this into CI. I'm not 100% sure how to hook it all up. I have some local scripts that do it how I imagine it should be as a proof of concept, but still need to figure out how to make that a reality. Will be the next step :)
teg: Addressed the comments, except for the changelog, that's still to be resolved.
teg: Reverted the changelog. We can discuss that separately.

Patch:
diff --git a/Makefile b/Makefile
index 1365b612c..f0d099378 100644
--- a/Makefile
+++ b/Makefile
@@ -15,6 +15,8 @@ install:
 	cp osbuild-composer /usr/libexec/osbuild-composer/
 	cp osbuild-worker /usr/libexec/osbuild-composer/
 	cp dnf-json /usr/libexec/osbuild-composer/
+	- mkdir -p /usr/share/osbuild-composer/repositories
+	cp repositories/* /usr/share/osbuild-composer/repositories
 	- mkdir -p /etc/sysusers.d/
 	cp distribution/osbuild-composer.conf /etc/sysusers.d/
 	systemd-sysusers osbuild-composer.conf
diff --git a/README.md b/README.md
index 1252ab833..53f457c3a 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,24 @@
 # osbuild-composer
 
-An image building service based on
-[osbuild](https://github.com/osbuild/osbuild).  It is inspired by
-[lorax-composer](https://github.com/weldr/lorax) and exposes the same API. As
-such, it is a drop-in replacement.
+An HTTP service for building bootable OS images. It provides the same API as [lorax-composer](https://github.com/weldr/lorax) but in the background it uses [osbuild](https://github.com/osbuild/osbuild) to create the images.
+
+You can control it in [Cockpit](https://github.com/weldr/cockpit-composer) or using the [composer-cli](https://weldr.io/lorax/composer-cli.html). To get started on Fedora, run:
+
+```
+# dnf install cockpit-composer golang-github-osbuild-composer composer-cli
+# systemctl enable --now cockpit.socket
+# systemctl enable --now osbuild-composer.socket
+```
+
+Now you can access the service using `composer-cli`, for example:
+
+```
+composer-cli status show
+```
+
+or using a browser: `http://localhost:9090`
+
+## API documentation
+
+Please refer to the [lorax-composer](https://github.com/weldr/lorax)'s documenation as osbuild-composer is a drop-in replacement.
+
diff --git a/cmd/osbuild-composer/main.go b/cmd/osbuild-composer/main.go
index d7217adec..cbeda673b 100644
--- a/cmd/osbuild-composer/main.go
+++ b/cmd/osbuild-composer/main.go
@@ -49,8 +49,9 @@ func main() {
 	jobListener := listeners[1]
 
 	rpm := rpmmd.NewRPMMD()
+	distros := distro.NewRegistry([]string{"/etc/osbuild-composer", "/usr/share/osbuild-composer"})
 
-	distribution, err := distro.FromHost()
+	distribution, err := distros.FromHost()
 	if err != nil {
 		log.Fatalf("Could not determine distro from host: " + err.Error())
 	}
diff --git a/cmd/osbuild-pipeline/main.go b/cmd/osbuild-pipeline/main.go
index 5b03dcce2..a5d0191da 100644
--- a/cmd/osbuild-pipeline/main.go
+++ b/cmd/osbuild-pipeline/main.go
@@ -34,7 +34,8 @@ func main() {
 		}
 	}
 
-	d := distro.New(distroArg)
+	distros := distro.NewRegistry([]string{"/etc/osbuild-composer", "/usr/share/osbuild-composer"})
+	d := distros.GetDistro(distroArg)
 	if d == nil {
 		panic("unknown distro: " + distroArg)
 	}
diff --git a/golang-github-osbuild-composer.spec b/golang-github-osbuild-composer.spec
index 27cc43217..312002b79 100644
--- a/golang-github-osbuild-composer.spec
+++ b/golang-github-osbuild-composer.spec
@@ -64,12 +64,20 @@ export GOFLAGS=-mod=vendor
 %endif
 %gobuild -o _bin/osbuild-composer %{goipath}/cmd/osbuild-composer
 %gobuild -o _bin/osbuild-worker %{goipath}/cmd/osbuild-worker
+%gobuild -o _bin/osbuild-tests %{goipath}/cmd/osbuild-tests
 
 %install
 install -m 0755 -vd                                         %{buildroot}%{_libexecdir}/osbuild-composer
-install -m 0755 -vp _bin/*                                  %{buildroot}%{_libexecdir}/osbuild-composer/
+install -m 0755 -vp _bin/osbuild-composer                   %{buildroot}%{_libexecdir}/osbuild-composer/
+install -m 0755 -vp _bin/osbuild-worker                     %{buildroot}%{_libexecdir}/osbuild-composer/
 install -m 0755 -vp dnf-json                                %{buildroot}%{_libexecdir}/osbuild-composer/
 
+install -m 0755 -vd                                         %{buildroot}%{_libexecdir}/tests/osbuild-composer
+install -m 0755 -vp _bin/osbuild-tests                      %{buildroot}%{_libexecdir}/tests/osbuild-composer/
+
+install -m 0755 -vd                                         %{buildroot}%{_datadir}/osbuild-composer/repositories
+install -m 0644 -vp repositories/*                          %{buildroot}%{_datadir}/osbuild-composer/repositories/
+
 install -m 0755 -vd                                         %{buildroot}%{_unitdir}
 install -m 0644 -vp distribution/*.{service,socket}         %{buildroot}%{_unitdir}/
 
@@ -100,9 +108,21 @@ export GOPATH=$PWD/_build:%{gopath}
 %license LICENSE
 %doc README.md
 %{_libexecdir}/osbuild-composer/
+%{_datadir}/osbuild-composer/
 %{_unitdir}/*.{service,socket}
 %{_sysusersdir}/osbuild-composer.conf
 
+%package tests
+Summary:	Integration tests
+Requires: 	osbuild-composer
+Requires: 	composer-cli
+
+%description tests
+Integration tests to be run on a pristine-dedicated system to test the osbuild-composer package.
+
+%files tests
+%{_libexecdir}/tests/osbuild-composer/osbuild-tests
+
 %changelog
 * Sun Dec 1 11:00:00 CEST 2019 Ondrej Budai <obudai@redhat.com> - 4-1
 - New upstream release.
diff --git a/internal/distro/distro.go b/internal/distro/distro.go
index e0f715433..d68541cdd 100644
--- a/internal/distro/distro.go
+++ b/internal/distro/distro.go
@@ -40,17 +40,29 @@ type Distro interface {
 	Runner() string
 }
 
-var registered map[string]Distro
+type Registry struct {
+	distros map[string]Distro
+}
 
-func init() {
-	registered = map[string]Distro{
-		fedora30.Name: fedora30.New(),
-		rhel82.Name:   rhel82.New(),
+func NewRegistry(confPaths []string) *Registry {
+	distros := &Registry{
+		distros: make(map[string]Distro),
+	}
+	distros.register(fedora30.New(confPaths))
+	distros.register(rhel82.New(confPaths))
+	return distros
+}
+
+func (r *Registry) register(distro Distro) {
+	name := distro.Name()
+	if _, exists := r.distros[name]; exists {
+		panic("a distro with this name already exists: " + name)
 	}
+	r.distros[name] = distro
 }
 
-func New(name string) Distro {
-	distro, ok := registered[name]
+func (r *Registry) GetDistro(name string) Distro {
+	distro, ok := r.distros[name]
 	if !ok {
 		return nil
 	}
@@ -58,7 +70,7 @@ func New(name string) Distro {
 	return distro
 }
 
-func FromHost() (Distro, error) {
+func (r *Registry) FromHost() (Distro, error) {
 	f, err := os.Open("/etc/os-release")
 	if err != nil {
 		return nil, err
@@ -72,7 +84,7 @@ func FromHost() (Distro, error) {
 
 	name := osrelease["ID"] + "-" + osrelease["VERSION_ID"]
 
-	d := New(name)
+	d := r.GetDistro(name)
 	if d == nil {
 		return nil, errors.New("unknown distro: " + name)
 	}
@@ -80,13 +92,6 @@ func FromHost() (Distro, error) {
 	return d, nil
 }
 
-func Register(name string, distro Distro) {
-	if _, exists := registered[name]; exists {
-		panic("a distro with this name already exists: " + name)
-	}
-	registered[name] = distro
-}
-
 func readOSRelease(r io.Reader) (map[string]string, error) {
 	osrelease := make(map[string]string)
 	scanner := bufio.NewScanner(r)
diff --git a/internal/distro/fedora30/distro.go b/internal/distro/fedora30/distro.go
index 822c02fac..99c990795 100644
--- a/internal/distro/fedora30/distro.go
+++ b/internal/distro/fedora30/distro.go
@@ -2,6 +2,7 @@ package fedora30
 
 import (
 	"errors"
+	"log"
 	"sort"
 	"strconv"
 
@@ -23,6 +24,7 @@ type arch struct {
 	BootloaderPackages []string
 	BuildPackages      []string
 	UEFI               bool
+	Repositories       []rpmmd.RepoConfig
 }
 
 type output struct {
@@ -39,32 +41,50 @@ type output struct {
 
 const Name = "fedora-30"
 
-func New() *Fedora30 {
+func New(confPaths []string) *Fedora30 {
 	r := Fedora30{
 		arches:  map[string]arch{},
 		outputs: map[string]output{},
 	}
 
-	r.arches["x86_64"] = arch{
-		Name: "x86_64",
-		BootloaderPackages: []string{
-			"grub2-pc",
-		},
-		BuildPackages: []string{
-			"grub2-pc",
-		},
+	repoMap, err := rpmmd.LoadRepositories(confPaths, Name)
+	if err != nil {
+		log.Printf("Could not load repository data for %s: %s", Name, err.Error())
+		return nil
 	}
 
-	r.arches["aarch64"] = arch{
-		Name: "aarch64",
-		BootloaderPackages: []string{
-			"dracut-config-generic",
-			"efibootmgr",
-			"grub2-efi-aa64",
-			"grub2-tools",
-			"shim-aa64",
-		},
-		UEFI: true,
+	repos, exists := repoMap["x86_64"]
+	if !exists {
+		log.Printf("Could not load architecture-specific repository data for x86_64 (%s): %s", Name, err.Error())
+	} else {
+		r.arches["x86_64"] = arch{
+			Name: "x86_64",
+			BootloaderPackages: []string{
+				"grub2-pc",
+			},
+			BuildPackages: []string{
+				"grub2-pc",
+			},
+			Repositories: repos,
+		}
+	}
+
+	repos, exists = repoMap["aarch64"]
+	if !exists {
+		log.Printf("Could not load architecture-specific repository data for x86_64 (%s): %s", Name, err.Error())
+	} else {
+		r.arches["aarch64"] = arch{
+			Name: "aarch64",
+			BootloaderPackages: []string{
+				"dracut-config-generic",
+				"efibootmgr",
+				"grub2-efi-aa64",
+				"grub2-tools",
+				"shim-aa64",
+			},
+			UEFI:         true,
+			Repositories: repos,
+		}
 	}
 
 	r.outputs["ami"] = output{
@@ -259,42 +279,7 @@ func (r *Fedora30) Name() string {
 }
 
 func (r *Fedora30) Repositories(arch string) []rpmmd.RepoConfig {
-	return []rpmmd.RepoConfig{
-		{
-			Id:       "fedora",
-			Name:     "Fedora 30",
-			Metalink: "https://mirrors.fedoraproject.org/metalink?repo=fedora-30&arch=" + arch,
-			GPGKey: `-----BEGIN PGP PUBLIC KEY BLOCK-----
-
-mQINBFturGcBEACv0xBo91V2n0uEC2vh69ywCiSyvUgN/AQH8EZpCVtM7NyjKgKm
-bbY4G3R0M3ir1xXmvUDvK0493/qOiFrjkplvzXFTGpPTi0ypqGgxc5d0ohRA1M75
-L+0AIlXoOgHQ358/c4uO8X0JAA1NYxCkAW1KSJgFJ3RjukrfqSHWthS1d4o8fhHy
-KJKEnirE5hHqB50dafXrBfgZdaOs3C6ppRIePFe2o4vUEapMTCHFw0woQR8Ah4/R
-n7Z9G9Ln+0Cinmy0nbIDiZJ+pgLAXCOWBfDUzcOjDGKvcpoZharA07c0q1/5ojzO
-4F0Fh4g/BUmtrASwHfcIbjHyCSr1j/3Iz883iy07gJY5Yhiuaqmp0o0f9fgHkG53
-2xCU1owmACqaIBNQMukvXRDtB2GJMuKa/asTZDP6R5re+iXs7+s9ohcRRAKGyAyc
-YKIQKcaA+6M8T7/G+TPHZX6HJWqJJiYB+EC2ERblpvq9TPlLguEWcmvjbVc31nyq
-SDoO3ncFWKFmVsbQPTbP+pKUmlLfJwtb5XqxNR5GEXSwVv4I7IqBmJz1MmRafnBZ
-g0FJUtH668GnldO20XbnSVBr820F5SISMXVwCXDXEvGwwiB8Lt8PvqzXnGIFDAu3
-DlQI5sxSqpPVWSyw08ppKT2Tpmy8adiBotLfaCFl2VTHwOae48X2dMPBvQARAQAB
-tDFGZWRvcmEgKDMwKSA8ZmVkb3JhLTMwLXByaW1hcnlAZmVkb3JhcHJvamVjdC5v
-cmc+iQI4BBMBAgAiBQJbbqxnAhsPBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAK
-CRDvPBEfz8ZZudTnD/9170LL3nyTVUCFmBjT9wZ4gYnpwtKVPa/pKnxbbS+Bmmac
-g9TrT9pZbqOHrNJLiZ3Zx1Hp+8uxr3Lo6kbYwImLhkOEDrf4aP17HfQ6VYFbQZI8
-f79OFxWJ7si9+3gfzeh9UYFEqOQfzIjLWFyfnas0OnV/P+RMQ1Zr+vPRqO7AR2va
-N9wg+Xl7157dhXPCGYnGMNSoxCbpRs0JNlzvJMuAea5nTTznRaJZtK/xKsqLn51D
-K07k9MHVFXakOH8QtMCUglbwfTfIpO5YRq5imxlWbqsYWVQy1WGJFyW6hWC0+RcJ
-Ox5zGtOfi4/dN+xJ+ibnbyvy/il7Qm+vyFhCYqIPyS5m2UVJUuao3eApE38k78/o
-8aQOTnFQZ+U1Sw+6woFTxjqRQBXlQm2+7Bt3bqGATg4sXXWPbmwdL87Ic+mxn/ml
-SMfQux/5k6iAu1kQhwkO2YJn9eII6HIPkW+2m5N1JsUyJQe4cbtZE5Yh3TRA0dm7
-+zoBRfCXkOW4krchbgww/ptVmzMMP7GINJdROrJnsGl5FVeid9qHzV7aZycWSma7
-CxBYB1J8HCbty5NjtD6XMYRrMLxXugvX6Q4NPPH+2NKjzX4SIDejS6JjgrP3KA3O
-pMuo7ZHMfveBngv8yP+ZD/1sS6l+dfExvdaJdOdgFCnp4p3gPbw5+Lv70HrMjA==
-=BfZ/
------END PGP PUBLIC KEY BLOCK-----
-`,
-		},
-	}
+	return r.arches[arch].Repositories
 }
 
 func (r *Fedora30) ListOutputFormats() []string {
@@ -420,7 +405,7 @@ func (r *Fedora30) dnfStageOptions(arch arch, additionalRepos []rpmmd.RepoConfig
 		ModulePlatformId: "platform:f30",
 	}
 
-	for _, repo := range append(r.Repositories(arch.Name), additionalRepos...) {
+	for _, repo := range append(arch.Repositories, additionalRepos...) {
 		options.AddRepository(&pipeline.DNFRepository{
 			BaseURL:    repo.BaseURL,
 			MetaLink:   repo.Metalink,
diff --git a/internal/distro/rhel82/distro.go b/internal/distro/rhel82/distro.go
index 80b3c3795..8845b3722 100644
--- a/internal/distro/rhel82/distro.go
+++ b/internal/distro/rhel82/distro.go
@@ -2,6 +2,7 @@ package rhel82
 
 import (
 	"errors"
+	"log"
 	"sort"
 	"strconv"
 
@@ -23,6 +24,7 @@ type arch struct {
 	BootloaderPackages []string
 	BuildPackages      []string
 	UEFI               bool
+	Repositories       []rpmmd.RepoConfig
 }
 
 type output struct {
@@ -40,7 +42,7 @@ type output struct {
 
 const Name = "rhel-8.2"
 
-func New() *RHEL82 {
+func New(confPaths []string) *RHEL82 {
 	const GigaByte = 1024 * 1024 * 1024
 
 	r := RHEL82{
@@ -48,26 +50,44 @@ func New() *RHEL82 {
 		outputs: map[string]output{},
 	}
 
-	r.arches["x86_64"] = arch{
-		Name: "x86_64",
-		BootloaderPackages: []string{
-			"grub2-pc",
-		},
-		BuildPackages: []string{
-			"grub2-pc",
-		},
+	repoMap, err := rpmmd.LoadRepositories(confPaths, Name)
+	if err != nil {
+		log.Printf("Could not load repository data for %s: %s", Name, err.Error())
+		return nil
 	}
 
-	r.arches["aarch64"] = arch{
-		Name: "aarch64",
-		BootloaderPackages: []string{
-			"dracut-config-generic",
-			"efibootmgr",
-			"grub2-efi-aa64",
-			"grub2-tools",
-			"shim-aa64",
-		},
-		UEFI: true,
+	repos, exists := repoMap["x86_64"]
+	if !exists {
+		log.Printf("Could not load architecture-specific repository data for x86_64 (%s): %s", Name, err.Error())
+	} else {
+		r.arches["x86_64"] = arch{
+			Name: "x86_64",
+			BootloaderPackages: []string{
+				"grub2-pc",
+			},
+			BuildPackages: []string{
+				"grub2-pc",
+			},
+			Repositories: repos,
+		}
+	}
+
+	repos, exists = repoMap["aarch64"]
+	if !exists {
+		log.Printf("Could not load architecture-specific repository data for aarch64 (%s): %s", Name, err.Error())
+	} else {
+		r.arches["aarch64"] = arch{
+			Name: "aarch64",
+			BootloaderPackages: []string{
+				"dracut-config-generic",
+				"efibootmgr",
+				"grub2-efi-aa64",
+				"grub2-tools",
+				"shim-aa64",
+			},
+			UEFI:         true,
+			Repositories: repos,
+		}
 	}
 
 	r.outputs["ami"] = output{
@@ -399,18 +419,7 @@ func (r *RHEL82) Name() string {
 }
 
 func (r *RHEL82) Repositories(arch string) []rpmmd.RepoConfig {
-	return []rpmmd.RepoConfig{
-		{
-			Id:      "baseos",
-			Name:    "BaseOS",
-			BaseURL: "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/" + arch + "/os",
-		},
-		{
-			Id:      "appstream",
-			Name:    "AppStream",
-			BaseURL: "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/" + arch + "/os",
-		},
-	}
+	return r.arches[arch].Repositories
 }
 
 func (r *RHEL82) ListOutputFormats() []string {
@@ -544,7 +553,7 @@ func (r *RHEL82) dnfStageOptions(arch arch, additionalRepos []rpmmd.RepoConfig,
 		BaseArchitecture: arch.Name,
 		ModulePlatformId: "platform:el8",
 	}
-	for _, repo := range append(r.Repositories(arch.Name), additionalRepos...) {
+	for _, repo := range append(arch.Repositories, additionalRepos...) {
 		options.AddRepository(&pipeline.DNFRepository{
 			BaseURL:    repo.BaseURL,
 			MetaLink:   repo.Metalink,
diff --git a/internal/jobqueue/job.go b/internal/jobqueue/job.go
index 7af970aa8..598b85e2a 100644
--- a/internal/jobqueue/job.go
+++ b/internal/jobqueue/job.go
@@ -30,7 +30,8 @@ type JobStatus struct {
 }
 
 func (job *Job) Run() (*store.Image, error, []error) {
-	d := distro.New(job.Distro)
+	distros := distro.NewRegistry([]string{"/etc/osbuild-composer", "/usr/share/osbuild-composer"})
+	d := distros.GetDistro(job.Distro)
 	if d == nil {
 		return nil, fmt.Errorf("unknown distro: %s", job.Distro), nil
 	}
diff --git a/internal/mocks/rpmmd/fixtures.go b/internal/mocks/rpmmd/fixtures.go
index 6f736e6e0..229743fc1 100644
--- a/internal/mocks/rpmmd/fixtures.go
+++ b/internal/mocks/rpmmd/fixtures.go
@@ -7,7 +7,7 @@ import (
 
 	"github.com/google/uuid"
 	"github.com/osbuild/osbuild-composer/internal/blueprint"
-	"github.com/osbuild/osbuild-composer/internal/distro"
+	test_distro "github.com/osbuild/osbuild-composer/internal/distro/test"
 	"github.com/osbuild/osbuild-composer/internal/rpmmd"
 	"github.com/osbuild/osbuild-composer/internal/store"
 	"github.com/osbuild/osbuild-composer/internal/target"
@@ -66,7 +66,6 @@ func createBaseStoreFixture() *store.Store {
 
 	var date = time.Date(2019, 11, 27, 13, 19, 0, 0, time.FixedZone("UTC+1", 60*60))
 
-
 	var localTarget = &target.Target{
 		Uuid:      uuid.MustParse("20000000-0000-0000-0000-000000000000"),
 		Name:      "org.osbuild.local",
@@ -93,7 +92,7 @@ func createBaseStoreFixture() *store.Store {
 		},
 	}
 
-	d := distro.New("fedora-30")
+	d := test_distro.New()
 	s := store.New(nil, d)
 
 	s.Blueprints[bName] = b
@@ -101,14 +100,14 @@ func createBaseStoreFixture() *store.Store {
 		uuid.MustParse("30000000-0000-0000-0000-000000000000"): store.Compose{
 			QueueStatus: "WAITING",
 			Blueprint:   &b,
-			OutputType:  "tar",
+			OutputType:  "test_output",
 			Targets:     []*target.Target{localTarget, awsTarget},
 			JobCreated:  date,
 		},
 		uuid.MustParse("30000000-0000-0000-0000-000000000001"): store.Compose{
 			QueueStatus: "RUNNING",
 			Blueprint:   &b,
-			OutputType:  "tar",
+			OutputType:  "test_output",
 			Targets:     []*target.Target{localTarget},
 			JobCreated:  date,
 			JobStarted:  date,
@@ -116,7 +115,7 @@ func createBaseStoreFixture() *store.Store {
 		uuid.MustParse("30000000-0000-0000-0000-000000000002"): store.Compose{
 			QueueStatus: "FINISHED",
 			Blueprint:   &b,
-			OutputType:  "tar",
+			OutputType:  "test_output",
 			Targets:     []*target.Target{localTarget, awsTarget},
 			JobCreated:  date,
 			JobStarted:  date,
@@ -125,7 +124,7 @@ func createBaseStoreFixture() *store.Store {
 		uuid.MustParse("30000000-0000-0000-0000-000000000003"): store.Compose{
 			QueueStatus: "FAILED",
 			Blueprint:   &b,
-			OutputType:  "tar",
+			OutputType:  "test_output",
 			Targets:     []*target.Target{localTarget, awsTarget},
 			JobCreated:  date,
 			JobStarted:  date,
@@ -166,7 +165,7 @@ func createStoreWithoutComposesFixture() *store.Store {
 		Customizations: nil,
 	}
 
-	d := distro.New("fedora-30")
+	d := test_distro.New()
 	s := store.New(nil, d)
 
 	s.Blueprints[bName] = b
@@ -247,7 +246,7 @@ func BadDepsolve() Fixture {
 func BadFetch() Fixture {
 	return Fixture{
 		fetchPackageList{
-			ret: nil,
+			ret:       nil,
 			checksums: nil,
 			err: &rpmmd.DNFError{
 				Kind:   "FetchError",
diff --git a/internal/rpmmd/repository.go b/internal/rpmmd/repository.go
index f976f238f..04f747666 100644
--- a/internal/rpmmd/repository.go
+++ b/internal/rpmmd/repository.go
@@ -106,6 +106,31 @@ func (err *DNFError) Error() string {
 	return fmt.Sprintf("DNF error occured: %s: %s", err.Kind, err.Reason)
 }
 
+func LoadRepositories(confPaths []string, distro string) (map[string][]RepoConfig, error) {
+	var f *os.File
+	var err error
+	path := "/repositories/" + distro + ".json"
+
+	for _, confPath := range confPaths {
+		f, err = os.Open(confPath + path)
+		if err == nil {
+			break
+		} else if !os.IsNotExist(err) {
+			return nil, err
+		}
+	}
+	defer f.Close()
+
+	var repos map[string][]RepoConfig
+
+	err = json.NewDecoder(f).Decode(&repos)
+	if err != nil {
+		return nil, err
+	}
+
+	return repos, nil
+}
+
 func runDNF(command string, arguments interface{}, result interface{}) error {
 	var call = struct {
 		Command   string      `json:"command"`
diff --git a/internal/store/store.go b/internal/store/store.go
index f8ea1e9e3..d276e9d59 100644
--- a/internal/store/store.go
+++ b/internal/store/store.go
@@ -119,7 +119,7 @@ func New(stateDir *string, distro distro.Distro) *Store {
 	var s Store
 
 	if stateDir != nil {
-		err := os.Mkdir(*stateDir+"/"+"outputs", 0755)
+		err := os.Mkdir(*stateDir+"/"+"outputs", 0700)
 		if err != nil && !os.IsExist(err) {
 			log.Fatalf("cannot create output directory")
 		}
@@ -140,7 +140,7 @@ func New(stateDir *string, distro distro.Distro) *Store {
 
 		go func() {
 			for {
-				err := writeFileAtomically(stateFile, <-s.stateChannel, 0755)
+				err := writeFileAtomically(stateFile, <-s.stateChannel, 0600)
 				if err != nil {
 					log.Fatalf("cannot write state: %v", err)
 				}
diff --git a/repositories/fedora-30.json b/repositories/fedora-30.json
new file mode 100644
index 000000000..69fe0c242
--- /dev/null
+++ b/repositories/fedora-30.json
@@ -0,0 +1,18 @@
+{
+    "x86_64": [
+        {
+            "id": "fedora",
+            "name": "Fedora 30",
+            "metalink": "https://mirrors.fedoraproject.org/metalink?repo=fedora-30&arch=x86_64",
+            "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFturGcBEACv0xBo91V2n0uEC2vh69ywCiSyvUgN/AQH8EZpCVtM7NyjKgKm\nbbY4G3R0M3ir1xXmvUDvK0493/qOiFrjkplvzXFTGpPTi0ypqGgxc5d0ohRA1M75\nL+0AIlXoOgHQ358/c4uO8X0JAA1NYxCkAW1KSJgFJ3RjukrfqSHWthS1d4o8fhHy\nKJKEnirE5hHqB50dafXrBfgZdaOs3C6ppRIePFe2o4vUEapMTCHFw0woQR8Ah4/R\nn7Z9G9Ln+0Cinmy0nbIDiZJ+pgLAXCOWBfDUzcOjDGKvcpoZharA07c0q1/5ojzO\n4F0Fh4g/BUmtrASwHfcIbjHyCSr1j/3Iz883iy07gJY5Yhiuaqmp0o0f9fgHkG53\n2xCU1owmACqaIBNQMukvXRDtB2GJMuKa/asTZDP6R5re+iXs7+s9ohcRRAKGyAyc\nYKIQKcaA+6M8T7/G+TPHZX6HJWqJJiYB+EC2ERblpvq9TPlLguEWcmvjbVc31nyq\nSDoO3ncFWKFmVsbQPTbP+pKUmlLfJwtb5XqxNR5GEXSwVv4I7IqBmJz1MmRafnBZ\ng0FJUtH668GnldO20XbnSVBr820F5SISMXVwCXDXEvGwwiB8Lt8PvqzXnGIFDAu3\nDlQI5sxSqpPVWSyw08ppKT2Tpmy8adiBotLfaCFl2VTHwOae48X2dMPBvQARAQAB\ntDFGZWRvcmEgKDMwKSA8ZmVkb3JhLTMwLXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQI4BBMBAgAiBQJbbqxnAhsPBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAK\nCRDvPBEfz8ZZudTnD/9170LL3nyTVUCFmBjT9wZ4gYnpwtKVPa/pKnxbbS+Bmmac\ng9TrT9pZbqOHrNJLiZ3Zx1Hp+8uxr3Lo6kbYwImLhkOEDrf4aP17HfQ6VYFbQZI8\nf79OFxWJ7si9+3gfzeh9UYFEqOQfzIjLWFyfnas0OnV/P+RMQ1Zr+vPRqO7AR2va\nN9wg+Xl7157dhXPCGYnGMNSoxCbpRs0JNlzvJMuAea5nTTznRaJZtK/xKsqLn51D\nK07k9MHVFXakOH8QtMCUglbwfTfIpO5YRq5imxlWbqsYWVQy1WGJFyW6hWC0+RcJ\nOx5zGtOfi4/dN+xJ+ibnbyvy/il7Qm+vyFhCYqIPyS5m2UVJUuao3eApE38k78/o\n8aQOTnFQZ+U1Sw+6woFTxjqRQBXlQm2+7Bt3bqGATg4sXXWPbmwdL87Ic+mxn/ml\nSMfQux/5k6iAu1kQhwkO2YJn9eII6HIPkW+2m5N1JsUyJQe4cbtZE5Yh3TRA0dm7\n+zoBRfCXkOW4krchbgww/ptVmzMMP7GINJdROrJnsGl5FVeid9qHzV7aZycWSma7\nCxBYB1J8HCbty5NjtD6XMYRrMLxXugvX6Q4NPPH+2NKjzX4SIDejS6JjgrP3KA3O\npMuo7ZHMfveBngv8yP+ZD/1sS6l+dfExvdaJdOdgFCnp4p3gPbw5+Lv70HrMjA==\n=BfZ/\n-----END PGP PUBLIC KEY BLOCK-----\n"
+        }
+    ],
+    "aarch64": [
+        {
+            "id": "fedora",
+            "name": "Fedora 30",
+            "metalink": "https://mirrors.fedoraproject.org/metalink?repo=fedora-30&arch=aarch64",
+            "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFturGcBEACv0xBo91V2n0uEC2vh69ywCiSyvUgN/AQH8EZpCVtM7NyjKgKm\nbbY4G3R0M3ir1xXmvUDvK0493/qOiFrjkplvzXFTGpPTi0ypqGgxc5d0ohRA1M75\nL+0AIlXoOgHQ358/c4uO8X0JAA1NYxCkAW1KSJgFJ3RjukrfqSHWthS1d4o8fhHy\nKJKEnirE5hHqB50dafXrBfgZdaOs3C6ppRIePFe2o4vUEapMTCHFw0woQR8Ah4/R\nn7Z9G9Ln+0Cinmy0nbIDiZJ+pgLAXCOWBfDUzcOjDGKvcpoZharA07c0q1/5ojzO\n4F0Fh4g/BUmtrASwHfcIbjHyCSr1j/3Iz883iy07gJY5Yhiuaqmp0o0f9fgHkG53\n2xCU1owmACqaIBNQMukvXRDtB2GJMuKa/asTZDP6R5re+iXs7+s9ohcRRAKGyAyc\nYKIQKcaA+6M8T7/G+TPHZX6HJWqJJiYB+EC2ERblpvq9TPlLguEWcmvjbVc31nyq\nSDoO3ncFWKFmVsbQPTbP+pKUmlLfJwtb5XqxNR5GEXSwVv4I7IqBmJz1MmRafnBZ\ng0FJUtH668GnldO20XbnSVBr820F5SISMXVwCXDXEvGwwiB8Lt8PvqzXnGIFDAu3\nDlQI5sxSqpPVWSyw08ppKT2Tpmy8adiBotLfaCFl2VTHwOae48X2dMPBvQARAQAB\ntDFGZWRvcmEgKDMwKSA8ZmVkb3JhLTMwLXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQI4BBMBAgAiBQJbbqxnAhsPBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAK\nCRDvPBEfz8ZZudTnD/9170LL3nyTVUCFmBjT9wZ4gYnpwtKVPa/pKnxbbS+Bmmac\ng9TrT9pZbqOHrNJLiZ3Zx1Hp+8uxr3Lo6kbYwImLhkOEDrf4aP17HfQ6VYFbQZI8\nf79OFxWJ7si9+3gfzeh9UYFEqOQfzIjLWFyfnas0OnV/P+RMQ1Zr+vPRqO7AR2va\nN9wg+Xl7157dhXPCGYnGMNSoxCbpRs0JNlzvJMuAea5nTTznRaJZtK/xKsqLn51D\nK07k9MHVFXakOH8QtMCUglbwfTfIpO5YRq5imxlWbqsYWVQy1WGJFyW6hWC0+RcJ\nOx5zGtOfi4/dN+xJ+ibnbyvy/il7Qm+vyFhCYqIPyS5m2UVJUuao3eApE38k78/o\n8aQOTnFQZ+U1Sw+6woFTxjqRQBXlQm2+7Bt3bqGATg4sXXWPbmwdL87Ic+mxn/ml\nSMfQux/5k6iAu1kQhwkO2YJn9eII6HIPkW+2m5N1JsUyJQe4cbtZE5Yh3TRA0dm7\n+zoBRfCXkOW4krchbgww/ptVmzMMP7GINJdROrJnsGl5FVeid9qHzV7aZycWSma7\nCxBYB1J8HCbty5NjtD6XMYRrMLxXugvX6Q4NPPH+2NKjzX4SIDejS6JjgrP3KA3O\npMuo7ZHMfveBngv8yP+ZD/1sS6l+dfExvdaJdOdgFCnp4p3gPbw5+Lv70HrMjA==\n=BfZ/\n-----END PGP PUBLIC KEY BLOCK-----\n"
+        }
+    ]
+}
diff --git a/repositories/rhel-8.2.json b/repositories/rhel-8.2.json
new file mode 100644
index 000000000..5df7aa943
--- /dev/null
+++ b/repositories/rhel-8.2.json
@@ -0,0 +1,26 @@
+{
+    "aarch64": [
+        {
+            "id": "baseos",
+            "name": "BaseOS",
+            "BaseURL": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/aarch64/os"
+        },
+        {
+            "id": "appstream",
+            "name": "AppStream",
+            "BaseURL": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/aarch64/os"
+        }
+    ],
+    "x86_64": [
+        {
+            "id": "baseos",
+            "name": "BaseOS",
+            "BaseURL": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os"
+        },
+        {
+            "id": "appstream",
+            "name": "AppStream",
+            "BaseURL": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os"
+        }
+    ]
+}


Test patch:
diff --git a/cmd/osbuild-tests/main.go b/cmd/osbuild-tests/main.go
new file mode 100644
index 000000000..8b1592623
--- /dev/null
+++ b/cmd/osbuild-tests/main.go
@@ -0,0 +1,249 @@
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"strings"
+	"time"
+
+	"github.com/BurntSushi/toml"
+	"github.com/google/uuid"
+	"github.com/osbuild/osbuild-composer/internal/blueprint"
+)
+
+func main() {
+	// Smoke tests, until functionality tested fully
+	// that the calls succeed and return valid output
+	runComposerCLI(false, "compose", "types")
+	runComposerCLI(false, "compose", "status")
+	runComposerCLI(false, "compose", "list")
+	runComposerCLI(false, "compose", "list", "waiting")
+	runComposerCLI(false, "compose", "list", "running")
+	runComposerCLI(false, "compose", "list", "finished")
+	runComposerCLI(false, "compose", "list", "failed")
+	// runCommand(false, "compose", "log", UUID, [<SIZE>])
+	// runCommand(false, "compose", "cancel", UUID)
+	// runCommand(false, "compose", "delete", UUID)
+	// runCommand(false, "compose", "info", UUID)
+	// runCommand(false, "compose", "metadata", UUID)
+	// runCommand(false, "compose", "logs", UUID)
+	// runCommand(false, "compose", "results", UUID)
+	// runCommand(false, "compose", "image", UUID)
+	runComposerCLI(false, "blueprints", "list")
+	// runCommand(false, "blueprints", "show", BLUEPRINT,....)
+	// runCommand(false, "blueprints", "changes", BLUEPRINT,....)
+	// runCommand(false, "blueprints", "diff", BLUEPRINT, FROM/NEWEST, TO/NEWEST/WORKSPACE)
+	// runCommand(false, "blueprints", "save", BLUEPRINT,...)
+	// runCommand(false, "blueprints", "delete", BLUEPRINT)
+	// runCommand(false, "blueprints", "depsolve", BLUEPRINT,...)
+	// runCommand(false, "blueprints", "push", BLUEPRINT.TOML)
+	// runCommand(false, "blueprints", "freeze", BLUEPRINT,...)
+	// runCommand(false, "blueprints", "freeze", "show", BLUEPRINT,...)
+	// runCommand(false, "blueprints", "freeze", "save", BLUEPRINT,...)
+	// runCommand(false, "blueprints", "tag", BLUEPRINT)
+	// runCommand(false, "blueprints", "undo", BLUEPRINT, COMMIT)
+	// runCommand(false, "blueprints", "workspace", BLUEPRINT)
+	runComposerCLI(false, "modules", "list")
+	runComposerCLI(false, "projects", "list")
+	runComposerCLI(false, "projects", "info", "filesystem")
+	runComposerCLI(false, "projects", "info", "filesystem", "kernel")
+	runComposerCLI(false, "sources", "list")
+	// runCommand(false, "sources", "info", "fedora")
+	// runCommand(false, "sources", "info", "fedora", "fedora-updates")
+	// runCommand(false, "sources", "add" SOURCES.TOML)
+	// runCommand(false, "sources", "change" SOURCES.TOML)
+	// runCommand(false, "sources", "delete" SOURCES.TOML)
+	runComposerCLI(false, "status", "show")
+
+	// Full integration tests
+	testCompose("qcow2")
+}
+
+func testCompose(outputType string) {
+	bp := blueprint.Blueprint{
+		Name:        "empty",
+		Description: "Test empty blueprint in toml format",
+	}
+	pushBlueprint(&bp)
+	defer deleteBlueprint(&bp)
+
+	uuid := startCompose("empty", outputType)
+	defer deleteCompose(uuid)
+	status := waitForCompose(uuid)
+	if status != "FINISHED" {
+		log.Fatalf("Unexpected compose result: %s", status)
+	}
+}
+
+func startCompose(name, outputType string) uuid.UUID {
+	var reply struct {
+		BuildID uuid.UUID `json:"build_id"`
+		Status  bool      `json:"status"`
+	}
+	rawReply := runComposerCLI(false, "compose", "start", name, outputType)
+	err := json.Unmarshal(rawReply, &reply)
+	if err != nil {
+		log.Fatalf("Unexpected reply: " + err.Error())
+	}
+	if reply.Status != true {
+		log.Fatalf("Unexpected status %v", reply.Status)
+	}
+
+	return reply.BuildID
+}
+
+func deleteCompose(id uuid.UUID) {
+	type deleteUUID struct {
+		ID     uuid.UUID `json:"uuid"`
+		Status bool      `json:"status"`
+	}
+	var reply struct {
+		IDs    []deleteUUID  `json:"uuids"`
+		Errors []interface{} `json:"errors"`
+	}
+	rawReply := runComposerCLI(false, "compose", "delete", id.String())
+	err := json.Unmarshal(rawReply, &reply)
+	if err != nil {
+		log.Fatalf("Unexpected reply: " + err.Error())
+	}
+	if len(reply.Errors) != 0 {
+		log.Fatalf("Unexpected errors")
+	}
+	if len(reply.IDs) != 1 {
+		log.Fatalf("Unexpected number of UUIDs returned: %d", len(reply.IDs))
+	}
+	if reply.IDs[0].Status != true {
+		log.Fatalf("Unexpected status %v", reply.IDs[0].Status)
+	}
+}
+
+func waitForCompose(uuid uuid.UUID) string {
+	for {
+		status := getComposeStatus(true, uuid)
+		if status == "FINISHED" || status == "FAILED" {
+			return status
+		}
+		time.Sleep(time.Second)
+	}
+}
+
+func getComposeStatus(quiet bool, uuid uuid.UUID) string {
+	var reply struct {
+		QueueStatus string `json:"queue_status"`
+	}
+	rawReply := runComposerCLI(quiet, "compose", "info", uuid.String())
+	err := json.Unmarshal(rawReply, &reply)
+	if err != nil {
+		log.Fatalf("Unexpected reply: " + err.Error())
+	}
+
+	return reply.QueueStatus
+}
+
+func pushBlueprint(bp *blueprint.Blueprint) {
+	tmpfile, err := ioutil.TempFile("", "osbuild-test-")
+	if err != nil {
+		log.Fatalf("Could not create temporary file: " + err.Error())
+	}
+	defer os.Remove(tmpfile.Name())
+
+	err = toml.NewEncoder(tmpfile).Encode(bp)
+	if err != nil {
+		log.Fatalf("Could not marshapl blueprint TOML: " + err.Error())
+	}
+	if err := tmpfile.Close(); err != nil {
+		log.Fatalf("Could not close toml file: " + err.Error())
+	}
+
+	var reply struct {
+		Status bool `json:"status"`
+	}
+	rawReply := runComposerCLI(false, "blueprints", "push", tmpfile.Name())
+	err = json.Unmarshal(rawReply, &reply)
+	if err != nil {
+		log.Fatalf("Unexpected reply: " + err.Error())
+	}
+	if reply.Status != true {
+		log.Fatalf("Unexpected status %v", reply.Status)
+	}
+}
+
+func deleteBlueprint(bp *blueprint.Blueprint) {
+	var reply struct {
+		Status bool `json:"status"`
+	}
+	rawReply := runComposerCLI(false, "blueprints", "delete", bp.Name)
+	err := json.Unmarshal(rawReply, &reply)
+	if err != nil {
+		log.Fatalf("Unexpected reply: " + err.Error())
+	}
+	if reply.Status != true {
+		log.Fatalf("Unexpected status %v", reply.Status)
+	}
+}
+
+func runComposerCLI(quiet bool, command ...string) json.RawMessage {
+	command = append([]string{"--json"}, command...)
+	cmd := exec.Command("composer-cli", command...)
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		log.Fatalf("Colud not create command: " + err.Error())
+	}
+
+	if !quiet {
+		log.Printf("$ composer-cli %s\n", strings.Join(command, " "))
+	}
+
+	cmd.Start()
+
+	var result json.RawMessage
+
+	contents, err := ioutil.ReadAll(stdout)
+	if err != nil {
+		log.Fatalf("Could not read stdout from command: " + err.Error())
+	}
+
+	if len(contents) != 0 {
+		err = json.Unmarshal(contents, &result)
+		if err != nil {
+			// We did not get JSON, try interpreting it as TOML
+			var data interface{}
+			err = toml.Unmarshal(contents, &data)
+			if err != nil {
+				log.Fatalf("Could not parse output: %s", err.Error())
+			}
+			buffer := bytes.Buffer{}
+			err = json.NewEncoder(&buffer).Encode(data)
+			if err != nil {
+				log.Fatalf("Could not remarshal TOML to JSON: %s", err.Error())
+			}
+			err = json.NewDecoder(&buffer).Decode(&result)
+			if err != nil {
+				log.Fatalf("Could not decode the remarshalled JSON: %s", err.Error())
+			}
+		}
+	}
+
+	err = cmd.Wait()
+	if err != nil {
+		log.Fatalf("Command failed: " + err.Error())
+	}
+
+	buffer := bytes.Buffer{}
+	encoder := json.NewEncoder(&buffer)
+	encoder.SetIndent("", "  ")
+	err = encoder.Encode(result)
+	if err != nil {
+		log.Fatalf("Could not remarshal the recevied JSON: " + err.Error())
+	}
+
+	if !quiet {
+		log.Printf("Return:\n%s\n", buffer.String())
+	}
+
+	return result
+}
diff --git a/internal/distro/distro_test.go b/internal/distro/distro_test.go
index 2e80c1e8a..57deef2b6 100644
--- a/internal/distro/distro_test.go
+++ b/internal/distro/distro_test.go
@@ -42,7 +42,8 @@ func TestDistro_Pipeline(t *testing.T) {
 			continue
 		}
 		t.Run(tt.Compose.OutputFormat, func(t *testing.T) {
-			d := distro.New(tt.Compose.Distro)
+			distros := distro.NewRegistry([]string{"../.."})
+			d := distros.GetDistro(tt.Compose.Distro)
 			if d == nil {
 				t.Errorf("unknown distro: %v", tt.Compose.Distro)
 				return
diff --git a/internal/distro/fedora30/distro_test.go b/internal/distro/fedora30/distro_test.go
index 56818dadd..1e6e69975 100644
--- a/internal/distro/fedora30/distro_test.go
+++ b/internal/distro/fedora30/distro_test.go
@@ -19,7 +19,8 @@ func TestListOutputFormats(t *testing.T) {
 		"vmdk",
 	}
 
-	f30 := distro.New("fedora-30")
+	distros := distro.NewRegistry([]string{"../../../"})
+	f30 := distros.GetDistro("fedora-30")
 	if got := f30.ListOutputFormats(); !reflect.DeepEqual(got, want) {
 		t.Errorf("ListOutputFormats() = %v, want %v", got, want)
 	}
@@ -92,7 +93,8 @@ func TestFilenameFromType(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			f30 := distro.New("fedora-30")
+			distros := distro.NewRegistry([]string{"../../../"})
+			f30 := distros.GetDistro("fedora-30")
 			got, got1, err := f30.FilenameFromType(tt.args.outputFormat)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("FilenameFromType() error = %v, wantErr %v", err, tt.wantErr)
diff --git a/internal/distro/rhel82/distro_test.go b/internal/distro/rhel82/distro_test.go
index 7aa2d24e6..3dea1259c 100644
--- a/internal/distro/rhel82/distro_test.go
+++ b/internal/distro/rhel82/distro_test.go
@@ -19,8 +19,9 @@ func TestListOutputFormats(t *testing.T) {
 		"vmdk",
 	}
 
-	f31 := distro.New("rhel-8.2")
-	if got := f31.ListOutputFormats(); !reflect.DeepEqual(got, want) {
+	distros := distro.NewRegistry([]string{"../../../"})
+	rhel82 := distros.GetDistro("rhel-8.2")
+	if got := rhel82.ListOutputFormats(); !reflect.DeepEqual(got, want) {
 		t.Errorf("ListOutputFormats() = %v, want %v", got, want)
 	}
 }
@@ -92,8 +93,9 @@ func TestFilenameFromType(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			f31 := distro.New("rhel-8.2")
-			got, got1, err := f31.FilenameFromType(tt.args.outputFormat)
+			distros := distro.NewRegistry([]string{"../../../"})
+			rhel82 := distros.GetDistro("rhel-8.2")
+			got, got1, err := rhel82.FilenameFromType(tt.args.outputFormat)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("FilenameFromType() error = %v, wantErr %v", err, tt.wantErr)
 				return
diff --git a/internal/distro/test/distro.go b/internal/distro/test/distro.go
index c56c1df11..732c2a879 100644
--- a/internal/distro/test/distro.go
+++ b/internal/distro/test/distro.go
@@ -4,17 +4,16 @@ import (
 	"errors"
 
 	"github.com/osbuild/osbuild-composer/internal/blueprint"
-	"github.com/osbuild/osbuild-composer/internal/distro"
 	"github.com/osbuild/osbuild-composer/internal/pipeline"
 	"github.com/osbuild/osbuild-composer/internal/rpmmd"
 )
 
 type TestDistro struct{}
 
-const Name = "test"
+const Name = "test-distro"
 
-func init() {
-	distro.Register(Name, &TestDistro{})
+func New() *TestDistro {
+	return &TestDistro{}
 }
 
 func (d *TestDistro) Name() string {
@@ -24,23 +23,31 @@ func (d *TestDistro) Name() string {
 func (d *TestDistro) Repositories(arch string) []rpmmd.RepoConfig {
 	return []rpmmd.RepoConfig{
 		{
-			Id:      "test",
-			Name:    "Test",
-			BaseURL: "http://example.com/test/os",
+			Id:      "test-id",
+			Name:    "Test Name",
+			BaseURL: "http://example.com/test/os/" + arch,
 		},
 	}
 }
 
 func (d *TestDistro) ListOutputFormats() []string {
-	return []string{}
+	return []string{"test_format"}
 }
 
 func (d *TestDistro) FilenameFromType(outputFormat string) (string, string, error) {
-	return "", "", errors.New("invalid output format: " + outputFormat)
+	if outputFormat == "test_format" {
+		return "test.img", "application/x-test", nil
+	} else {
+		return "", "", errors.New("invalid output format: " + outputFormat)
+	}
 }
 
 func (d *TestDistro) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.RepoConfig, checksums map[string]string, outputArch, outputFormat string) (*pipeline.Pipeline, error) {
-	return nil, errors.New("invalid output format or arch: " + outputFormat + " @ " + outputArch)
+	if outputFormat == "test_output" && outputArch == "test_arch" {
+		return &pipeline.Pipeline{}, nil
+	} else {
+		return nil, errors.New("invalid output format or arch: " + outputFormat + " @ " + outputArch)
+	}
 }
 
 func (d *TestDistro) Runner() string {
diff --git a/internal/jobqueue/api_test.go b/internal/jobqueue/api_test.go
index 80e75e221..b04b61c46 100644
--- a/internal/jobqueue/api_test.go
+++ b/internal/jobqueue/api_test.go
@@ -5,7 +5,7 @@ import (
 	"testing"
 
 	"github.com/osbuild/osbuild-composer/internal/blueprint"
-	"github.com/osbuild/osbuild-composer/internal/distro"
+	test_distro "github.com/osbuild/osbuild-composer/internal/distro/test"
 	"github.com/osbuild/osbuild-composer/internal/jobqueue"
 	"github.com/osbuild/osbuild-composer/internal/store"
 	"github.com/osbuild/osbuild-composer/internal/test"
@@ -32,7 +32,8 @@ func TestBasic(t *testing.T) {
 	}
 
 	for _, c := range cases {
-		api := jobqueue.New(nil, store.New(nil, distro.New("fedora-30")))
+		distro := test_distro.New()
+		api := jobqueue.New(nil, store.New(nil, distro))
 
 		test.TestRoute(t, api, false, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
 	}
@@ -40,25 +41,27 @@ func TestBasic(t *testing.T) {
 
 func TestCreate(t *testing.T) {
 	id, _ := uuid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff")
-	store := store.New(nil, distro.New("fedora-30"))
+	distro := test_distro.New()
+	store := store.New(nil, distro)
 	api := jobqueue.New(nil, store)
 
-	err := store.PushCompose(id, &blueprint.Blueprint{}, map[string]string{"fedora": "test:foo"}, "x86_64", "tar", nil)
+	err := store.PushCompose(id, &blueprint.Blueprint{}, map[string]string{"test-repo": "test:foo"}, "test_arch", "test_output", nil)
 	if err != nil {
 		t.Fatalf("error pushing compose: %v", err)
 	}
 
 	test.TestRoute(t, api, false, "POST", "/job-queue/v1/jobs", `{}`, http.StatusCreated,
-		`{"distro":"fedora-30","id":"ffffffff-ffff-ffff-ffff-ffffffffffff","pipeline":{"build":{"pipeline":{"stages":[{"name":"org.osbuild.dnf","options":{"repos":[{"metalink":"https://mirrors.fedoraproject.org/metalink?repo=fedora-30\u0026arch=x86_64","gpgkey":"-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFturGcBEACv0xBo91V2n0uEC2vh69ywCiSyvUgN/AQH8EZpCVtM7NyjKgKm\nbbY4G3R0M3ir1xXmvUDvK0493/qOiFrjkplvzXFTGpPTi0ypqGgxc5d0ohRA1M75\nL+0AIlXoOgHQ358/c4uO8X0JAA1NYxCkAW1KSJgFJ3RjukrfqSHWthS1d4o8fhHy\nKJKEnirE5hHqB50dafXrBfgZdaOs3C6ppRIePFe2o4vUEapMTCHFw0woQR8Ah4/R\nn7Z9G9Ln+0Cinmy0nbIDiZJ+pgLAXCOWBfDUzcOjDGKvcpoZharA07c0q1/5ojzO\n4F0Fh4g/BUmtrASwHfcIbjHyCSr1j/3Iz883iy07gJY5Yhiuaqmp0o0f9fgHkG53\n2xCU1owmACqaIBNQMukvXRDtB2GJMuKa/asTZDP6R5re+iXs7+s9ohcRRAKGyAyc\nYKIQKcaA+6M8T7/G+TPHZX6HJWqJJiYB+EC2ERblpvq9TPlLguEWcmvjbVc31nyq\nSDoO3ncFWKFmVsbQPTbP+pKUmlLfJwtb5XqxNR5GEXSwVv4I7IqBmJz1MmRafnBZ\ng0FJUtH668GnldO20XbnSVBr820F5SISMXVwCXDXEvGwwiB8Lt8PvqzXnGIFDAu3\nDlQI5sxSqpPVWSyw08ppKT2Tpmy8adiBotLfaCFl2VTHwOae48X2dMPBvQARAQAB\ntDFGZWRvcmEgKDMwKSA8ZmVkb3JhLTMwLXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQI4BBMBAgAiBQJbbqxnAhsPBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAK\nCRDvPBEfz8ZZudTnD/9170LL3nyTVUCFmBjT9wZ4gYnpwtKVPa/pKnxbbS+Bmmac\ng9TrT9pZbqOHrNJLiZ3Zx1Hp+8uxr3Lo6kbYwImLhkOEDrf4aP17HfQ6VYFbQZI8\nf79OFxWJ7si9+3gfzeh9UYFEqOQfzIjLWFyfnas0OnV/P+RMQ1Zr+vPRqO7AR2va\nN9wg+Xl7157dhXPCGYnGMNSoxCbpRs0JNlzvJMuAea5nTTznRaJZtK/xKsqLn51D\nK07k9MHVFXakOH8QtMCUglbwfTfIpO5YRq5imxlWbqsYWVQy1WGJFyW6hWC0+RcJ\nOx5zGtOfi4/dN+xJ+ibnbyvy/il7Qm+vyFhCYqIPyS5m2UVJUuao3eApE38k78/o\n8aQOTnFQZ+U1Sw+6woFTxjqRQBXlQm2+7Bt3bqGATg4sXXWPbmwdL87Ic+mxn/ml\nSMfQux/5k6iAu1kQhwkO2YJn9eII6HIPkW+2m5N1JsUyJQe4cbtZE5Yh3TRA0dm7\n+zoBRfCXkOW4krchbgww/ptVmzMMP7GINJdROrJnsGl5FVeid9qHzV7aZycWSma7\nCxBYB1J8HCbty5NjtD6XMYRrMLxXugvX6Q4NPPH+2NKjzX4SIDejS6JjgrP3KA3O\npMuo7ZHMfveBngv8yP+ZD/1sS6l+dfExvdaJdOdgFCnp4p3gPbw5+Lv70HrMjA==\n=BfZ/\n-----END PGP PUBLIC KEY BLOCK-----\n","checksum":"test:foo"}],"packages":["dnf","dosfstools","e2fsprogs","grub2-pc","policycoreutils","qemu-img","systemd","tar"],"releasever":"30","basearch":"x86_64","module_platform_id":"platform:f30"}}]},"runner":"org.osbuild.fedora30"},"stages":[{"name":"org.osbuild.dnf","options":{"repos":[{"metalink":"https://mirrors.fedoraproject.org/metalink?repo=fedora-30\u0026arch=x86_64","gpgkey":"-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBFturGcBEACv0xBo91V2n0uEC2vh69ywCiSyvUgN/AQH8EZpCVtM7NyjKgKm\nbbY4G3R0M3ir1xXmvUDvK0493/qOiFrjkplvzXFTGpPTi0ypqGgxc5d0ohRA1M75\nL+0AIlXoOgHQ358/c4uO8X0JAA1NYxCkAW1KSJgFJ3RjukrfqSHWthS1d4o8fhHy\nKJKEnirE5hHqB50dafXrBfgZdaOs3C6ppRIePFe2o4vUEapMTCHFw0woQR8Ah4/R\nn7Z9G9Ln+0Cinmy0nbIDiZJ+pgLAXCOWBfDUzcOjDGKvcpoZharA07c0q1/5ojzO\n4F0Fh4g/BUmtrASwHfcIbjHyCSr1j/3Iz883iy07gJY5Yhiuaqmp0o0f9fgHkG53\n2xCU1owmACqaIBNQMukvXRDtB2GJMuKa/asTZDP6R5re+iXs7+s9ohcRRAKGyAyc\nYKIQKcaA+6M8T7/G+TPHZX6HJWqJJiYB+EC2ERblpvq9TPlLguEWcmvjbVc31nyq\nSDoO3ncFWKFmVsbQPTbP+pKUmlLfJwtb5XqxNR5GEXSwVv4I7IqBmJz1MmRafnBZ\ng0FJUtH668GnldO20XbnSVBr820F5SISMXVwCXDXEvGwwiB8Lt8PvqzXnGIFDAu3\nDlQI5sxSqpPVWSyw08ppKT2Tpmy8adiBotLfaCFl2VTHwOae48X2dMPBvQARAQAB\ntDFGZWRvcmEgKDMwKSA8ZmVkb3JhLTMwLXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQI4BBMBAgAiBQJbbqxnAhsPBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAK\nCRDvPBEfz8ZZudTnD/9170LL3nyTVUCFmBjT9wZ4gYnpwtKVPa/pKnxbbS+Bmmac\ng9TrT9pZbqOHrNJLiZ3Zx1Hp+8uxr3Lo6kbYwImLhkOEDrf4aP17HfQ6VYFbQZI8\nf79OFxWJ7si9+3gfzeh9UYFEqOQfzIjLWFyfnas0OnV/P+RMQ1Zr+vPRqO7AR2va\nN9wg+Xl7157dhXPCGYnGMNSoxCbpRs0JNlzvJMuAea5nTTznRaJZtK/xKsqLn51D\nK07k9MHVFXakOH8QtMCUglbwfTfIpO5YRq5imxlWbqsYWVQy1WGJFyW6hWC0+RcJ\nOx5zGtOfi4/dN+xJ+ibnbyvy/il7Qm+vyFhCYqIPyS5m2UVJUuao3eApE38k78/o\n8aQOTnFQZ+U1Sw+6woFTxjqRQBXlQm2+7Bt3bqGATg4sXXWPbmwdL87Ic+mxn/ml\nSMfQux/5k6iAu1kQhwkO2YJn9eII6HIPkW+2m5N1JsUyJQe4cbtZE5Yh3TRA0dm7\n+zoBRfCXkOW4krchbgww/ptVmzMMP7GINJdROrJnsGl5FVeid9qHzV7aZycWSma7\nCxBYB1J8HCbty5NjtD6XMYRrMLxXugvX6Q4NPPH+2NKjzX4SIDejS6JjgrP3KA3O\npMuo7ZHMfveBngv8yP+ZD/1sS6l+dfExvdaJdOdgFCnp4p3gPbw5+Lv70HrMjA==\n=BfZ/\n-----END PGP PUBLIC KEY BLOCK-----\n","checksum":"test:foo"}],"packages":["chrony","firewalld","kernel","langpacks-en","policycoreutils","selinux-policy-targeted"],"exclude_packages":["dracut-config-rescue"],"releasever":"30","basearch":"x86_64","module_platform_id":"platform:f30"}},{"name":"org.osbuild.fix-bls","options":{}},{"name":"org.osbuild.locale","options":{"language":"en_US"}},{"name":"org.osbuild.grub2","options":{"root_fs_uuid":"76a22bf4-f153-4541-b6c7-0332c0dfaeac","kernel_opts":"ro biosdevname=0 net.ifnames=0","legacy":true}},{"name":"org.osbuild.selinux","options":{"file_contexts":"etc/selinux/targeted/contexts/files/file_contexts"}}],"assembler":{"name":"org.osbuild.tar","options":{"filename":"root.tar.xz","compression":"xz"}}},"targets":[],"output_type":"tar"}`, "created", "uuid")
+		`{"distro":"test-distro","id":"ffffffff-ffff-ffff-ffff-ffffffffffff","output_type":"test_output","pipeline":{},"targets":[]}`, "created", "uuid")
 }
 
 func testUpdateTransition(t *testing.T, from, to string, expectedStatus int) {
 	id, _ := uuid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff")
-	store := store.New(nil, distro.New("fedora-30"))
+	distro := test_distro.New()
+	store := store.New(nil, distro)
 	api := jobqueue.New(nil, store)
 
 	if from != "VOID" {
-		err := store.PushCompose(id, &blueprint.Blueprint{}, map[string]string{"fedora": "test:foo"}, "x86_64", "tar", nil)
+		err := store.PushCompose(id, &blueprint.Blueprint{}, map[string]string{"test": "test:foo"}, "test_arch", "test_output", nil)
 		if err != nil {
 			t.Fatalf("error pushing compose: %v", err)
 		}
diff --git a/internal/weldr/api_test.go b/internal/weldr/api_test.go
index 340528f29..b0cb33a87 100644
--- a/internal/weldr/api_test.go
+++ b/internal/weldr/api_test.go
@@ -13,8 +13,8 @@ import (
 	"time"
 
 	"github.com/osbuild/osbuild-composer/internal/blueprint"
-	"github.com/osbuild/osbuild-composer/internal/distro"
 	_ "github.com/osbuild/osbuild-composer/internal/distro/test"
+	test_distro "github.com/osbuild/osbuild-composer/internal/distro/test"
 	rpmmd_mock "github.com/osbuild/osbuild-composer/internal/mocks/rpmmd"
 	"github.com/osbuild/osbuild-composer/internal/store"
 	"github.com/osbuild/osbuild-composer/internal/target"
@@ -29,9 +29,9 @@ import (
 func createWeldrAPI(fixtureGenerator rpmmd_mock.FixtureGenerator) (*weldr.API, *store.Store) {
 	fixture := fixtureGenerator()
 	rpm := rpmmd_mock.NewRPMMDMock(fixture)
-	d := distro.New("test")
+	d := test_distro.New()
 
-	return weldr.New(rpm, "x86_64", d, nil, fixture.Store), fixture.Store
+	return weldr.New(rpm, "test_arch", d, nil, fixture.Store), fixture.Store
 }
 
 func TestBasic(t *testing.T) {
@@ -42,13 +42,13 @@ func TestBasic(t *testing.T) {
 	}{
 		{"/api/status", http.StatusOK, `{"api":"1","db_supported":true,"db_version":"0","schema_version":"0","backend":"osbuild-composer","build":"devel","messages":[]}`},
 
-		{"/api/v0/projects/source/list", http.StatusOK, `{"sources":["test"]}`},
+		{"/api/v0/projects/source/list", http.StatusOK, `{"sources":["test-id"]}`},
 
 		{"/api/v0/projects/source/info", http.StatusNotFound, ``},
 		{"/api/v0/projects/source/info/", http.StatusNotFound, `{"errors":[{"code":404,"id":"HTTPError","msg":"Not Found"}],"status":false}`},
 		{"/api/v0/projects/source/info/foo", http.StatusOK, `{"errors":[{"id":"UnknownSource","msg":"foo is not a valid source"}],"sources":{}}`},
-		{"/api/v0/projects/source/info/test", http.StatusOK, `{"sources":{"test":{"name":"test","type":"yum-baseurl","url":"http://example.com/test/os","check_gpg":true,"check_ssl":true,"system":true}},"errors":[]}`},
-		{"/api/v0/projects/source/info/*", http.StatusOK, `{"sources":{"test":{"name":"test","type":"yum-baseurl","url":"http://example.com/test/os","check_gpg":true,"check_ssl":true,"system":true}},"errors":[]}`},
+		{"/api/v0/projects/source/info/test-id", http.StatusOK, `{"sources":{"test-id":{"name":"test-id","type":"yum-baseurl","url":"http://example.com/test/os/test_arch","check_gpg":true,"check_ssl":true,"system":true}},"errors":[]}`},
+		{"/api/v0/projects/source/info/*", http.StatusOK, `{"sources":{"test-id":{"name":"test-id","type":"yum-baseurl","url":"http://example.com/test/os/test_arch","check_gpg":true,"check_ssl":true,"system":true}},"errors":[]}`},
 
 		{"/api/v0/blueprints/list", http.StatusOK, `{"total":1,"offset":0,"limit":1,"blueprints":["test"]}`},
 		{"/api/v0/blueprints/info/", http.StatusNotFound, `{"errors":[{"code":404,"id":"HTTPError","msg":"Not Found"}],"status":false}`},
@@ -280,10 +280,9 @@ func TestCompose(t *testing.T) {
 			Groups:         []blueprint.Group{},
 			Customizations: nil,
 		},
-		OutputType: "tar",
+		OutputType: "test_output",
 		Targets:    []*target.Target{},
 	}
-
 	expectedComposeLocalAndAws := &store.Compose{
 		QueueStatus: "WAITING",
 		Blueprint: &blueprint.Blueprint{
@@ -294,7 +293,7 @@ func TestCompose(t *testing.T) {
 			Groups:         []blueprint.Group{},
 			Customizations: nil,
 		},
-		OutputType: "tar",
+		OutputType: "test_output",
 		Targets: []*target.Target{
 			{
 				Name:      "org.osbuild.aws",
@@ -321,9 +320,9 @@ func TestCompose(t *testing.T) {
 		ExpectedCompose *store.Compose
 		IgnoreFields    []string
 	}{
-		{true, "POST", "/api/v0/compose", `{"blueprint_name": "http-server","compose_type": "tar","branch": "master"}`, http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownBlueprint","msg":"Unknown blueprint name: http-server"}]}`, nil, []string{"build_id"}},
-		{false, "POST", "/api/v0/compose", `{"blueprint_name": "test","compose_type": "tar","branch": "master"}`, http.StatusOK, `{"status": true}`, expectedComposeLocal, []string{"build_id"}},
-		{false, "POST", "/api/v1/compose", `{"blueprint_name": "test","compose_type":"tar","branch":"master","upload":{"image_name":"test_upload","provider":"aws","settings":{"region":"frankfurt","accessKeyID":"accesskey","secretAccessKey":"secretkey","bucket":"clay","key":"imagekey"}}}`, http.StatusOK, `{"status": true}`, expectedComposeLocalAndAws, []string{"build_id"}},
+		{true, "POST", "/api/v0/compose", `{"blueprint_name": "http-server","compose_type": "test_output","branch": "master"}`, http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownBlueprint","msg":"Unknown blueprint name: http-server"}]}`, nil, []string{"build_id"}},
+		{false, "POST", "/api/v0/compose", `{"blueprint_name": "test","compose_type": "test_output","branch": "master"}`, http.StatusOK, `{"status": true}`, expectedComposeLocal, []string{"build_id"}},
+		{false, "POST", "/api/v1/compose", `{"blueprint_name": "test","compose_type":"test_output","branch":"master","upload":{"image_name":"test_upload","provider":"aws","settings":{"region":"frankfurt","accessKeyID":"accesskey","secretAccessKey":"secretkey","bucket":"clay","key":"imagekey"}}}`, http.StatusOK, `{"status": true}`, expectedComposeLocalAndAws, []string{"build_id"}},
 	}
 
 	for _, c := range cases {
@@ -403,9 +402,9 @@ func TestComposeStatus(t *testing.T) {
 		ExpectedStatus int
 		ExpectedJSON   string
 	}{
-		{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/status/30000000-0000-0000-0000-000000000000,30000000-0000-0000-0000-000000000002", ``, http.StatusOK, `{"uuids":[{"id":"30000000-0000-0000-0000-000000000000","blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"WAITING","job_created":1574857140},{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`},
-		{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/status/*", ``, http.StatusOK, `{"uuids":[{"id":"30000000-0000-0000-0000-000000000000","blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"WAITING","job_created":1574857140},{"id":"30000000-0000-0000-0000-000000000001","blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"RUNNING","job_created":1574857140,"job_started":1574857140},{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140},{"id":"30000000-0000-0000-0000-000000000003","blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"FAILED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`},
-		{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/status/30000000-0000-0000-0000-000000000000", ``, http.StatusOK, `{"uuids":[{"id":"30000000-0000-0000-0000-000000000000","blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"WAITING","job_created":1574857140,"uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"WAITING","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","accessKeyID":"accesskey","secretAccessKey":"secretkey","bucket":"clay","key":"imagekey"}}]}]}`},
+		{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/status/30000000-0000-0000-0000-000000000000,30000000-0000-0000-0000-000000000002", ``, http.StatusOK, `{"uuids":[{"id":"30000000-0000-0000-0000-000000000000","blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"WAITING","job_created":1574857140},{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`},
+		{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/status/*", ``, http.StatusOK, `{"uuids":[{"id":"30000000-0000-0000-0000-000000000000","blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"WAITING","job_created":1574857140},{"id":"30000000-0000-0000-0000-000000000001","blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"RUNNING","job_created":1574857140,"job_started":1574857140},{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140},{"id":"30000000-0000-0000-0000-000000000003","blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"FAILED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`},
+		{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/status/30000000-0000-0000-0000-000000000000", ``, http.StatusOK, `{"uuids":[{"id":"30000000-0000-0000-0000-000000000000","blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"WAITING","job_created":1574857140,"uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"WAITING","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","accessKeyID":"accesskey","secretAccessKey":"secretkey","bucket":"clay","key":"imagekey"}}]}]}`},
 	}
 
 	if len(os.Getenv("OSBUILD_COMPOSER_TEST_EXTERNAL")) > 0 {
@@ -427,8 +426,8 @@ func TestComposeInfo(t *testing.T) {
 		ExpectedStatus int
 		ExpectedJSON   string
 	}{
-		{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/info/30000000-0000-0000-0000-000000000000", ``, http.StatusOK, `{"id":"30000000-0000-0000-0000-000000000000","config":"","blueprint":{"name":"test","description":"","version":"0.0.0","packages":[],"modules":[],"groups":[]},"commit":"","deps":{"packages":[]},"compose_type":"tar","queue_status":"WAITING","image_size":0}`},
-		{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/info/30000000-0000-0000-0000-000000000000", ``, http.StatusOK, `{"id":"30000000-0000-0000-0000-000000000000","config":"","blueprint":{"name":"test","description":"","version":"0.0.0","packages":[],"modules":[],"groups":[]},"commit":"","deps":{"packages":[]},"compose_type":"tar","queue_status":"WAITING","image_size":0,"uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"WAITING","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","accessKeyID":"accesskey","secretAccessKey":"secretkey","bucket":"clay","key":"imagekey"}}]}`},
+		{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/info/30000000-0000-0000-0000-000000000000", ``, http.StatusOK, `{"id":"30000000-0000-0000-0000-000000000000","config":"","blueprint":{"name":"test","description":"","version":"0.0.0","packages":[],"modules":[],"groups":[]},"commit":"","deps":{"packages":[]},"compose_type":"test_output","queue_status":"WAITING","image_size":0}`},
+		{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/info/30000000-0000-0000-0000-000000000000", ``, http.StatusOK, `{"id":"30000000-0000-0000-0000-000000000000","config":"","blueprint":{"name":"test","description":"","version":"0.0.0","packages":[],"modules":[],"groups":[]},"commit":"","deps":{"packages":[]},"compose_type":"test_output","queue_status":"WAITING","image_size":0,"uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"WAITING","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","accessKeyID":"accesskey","secretAccessKey":"secretkey","bucket":"clay","key":"imagekey"}}]}`},
 		{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/info/30000000-0000-0000-0000", ``, http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownUUID","msg":"30000000-0000-0000-0000 is not a valid build uuid"}]}`},
 		{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/info/42000000-0000-0000-0000-000000000000", ``, http.StatusBadRequest, `{"status":false,"errors":[{"id":"UnknownUUID","msg":"42000000-0000-0000-0000-000000000000 is not a valid build uuid"}]}`},
 	}
@@ -549,8 +548,8 @@ func TestComposeQueue(t *testing.T) {
 		ExpectedStatus int
 		ExpectedJSON   string
 	}{
-		{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/queue", ``, http.StatusOK, `{"new":[{"blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"WAITING"}],"run":[{"blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"RUNNING"}]}`},
-		{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/queue", ``, http.StatusOK, `{"new":[{"blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"WAITING","uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"WAITING","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","accessKeyID":"accesskey","secretAccessKey":"secretkey","bucket":"clay","key":"imagekey"}}]}],"run":[{"blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"RUNNING"}]}`},
+		{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/queue", ``, http.StatusOK, `{"new":[{"blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"WAITING"}],"run":[{"blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"RUNNING"}]}`},
+		{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/queue", ``, http.StatusOK, `{"new":[{"blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"WAITING","uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"WAITING","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","accessKeyID":"accesskey","secretAccessKey":"secretkey","bucket":"clay","key":"imagekey"}}]}],"run":[{"blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"RUNNING"}]}`},
 		{rpmmd_mock.NoComposesFixture, "GET", "/api/v0/compose/queue", ``, http.StatusOK, `{"new":[],"run":[]}`},
 	}
 
@@ -573,8 +572,8 @@ func TestComposeFinished(t *testing.T) {
 		ExpectedStatus int
 		ExpectedJSON   string
 	}{
-		{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/finished", ``, http.StatusOK, `{"finished":[{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`},
-		{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/finished", ``, http.StatusOK, `{"finished":[{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140,"uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"WAITING","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","accessKeyID":"accesskey","secretAccessKey":"secretkey","bucket":"clay","key":"imagekey"}}]}]}`},
+		{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/finished", ``, http.StatusOK, `{"finished":[{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`},
+		{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/finished", ``, http.StatusOK, `{"finished":[{"id":"30000000-0000-0000-0000-000000000002","blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"FINISHED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140,"uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"WAITING","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","accessKeyID":"accesskey","secretAccessKey":"secretkey","bucket":"clay","key":"imagekey"}}]}]}`},
 		{rpmmd_mock.NoComposesFixture, "GET", "/api/v0/compose/finished", ``, http.StatusOK, `{"finished":[]}`},
 	}
 
@@ -597,8 +596,8 @@ func TestComposeFailed(t *testing.T) {
 		ExpectedStatus int
 		ExpectedJSON   string
 	}{
-		{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/failed", ``, http.StatusOK, `{"failed":[{"id":"30000000-0000-0000-0000-000000000003","blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"FAILED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`},
-		{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/failed", ``, http.StatusOK, `{"failed":[{"id":"30000000-0000-0000-0000-000000000003","blueprint":"test","version":"0.0.0","compose_type":"tar","image_size":0,"queue_status":"FAILED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140,"uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"WAITING","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","accessKeyID":"accesskey","secretAccessKey":"secretkey","bucket":"clay","key":"imagekey"}}]}]}`},
+		{rpmmd_mock.BaseFixture, "GET", "/api/v0/compose/failed", ``, http.StatusOK, `{"failed":[{"id":"30000000-0000-0000-0000-000000000003","blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"FAILED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140}]}`},
+		{rpmmd_mock.BaseFixture, "GET", "/api/v1/compose/failed", ``, http.StatusOK, `{"failed":[{"id":"30000000-0000-0000-0000-000000000003","blueprint":"test","version":"0.0.0","compose_type":"test_output","image_size":0,"queue_status":"FAILED","job_created":1574857140,"job_started":1574857140,"job_finished":1574857140,"uploads":[{"uuid":"10000000-0000-0000-0000-000000000000","status":"WAITING","provider_name":"aws","image_name":"awsimage","creation_time":1574857140,"settings":{"region":"frankfurt","accessKeyID":"accesskey","secretAccessKey":"secretkey","bucket":"clay","key":"imagekey"}}]}]}`},
 		{rpmmd_mock.NoComposesFixture, "GET", "/api/v0/compose/failed", ``, http.StatusOK, `{"failed":[]}`},
 	}
 
diff --git a/test/cases/rhel82_ami.json b/test/cases/rhel82_ami.json
index 9cb9a8657..c37cb88fc 100644
--- a/test/cases/rhel82_ami.json
+++ b/test/cases/rhel82_ami.json
@@ -22,11 +22,11 @@
             "options": {
               "repos": [
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
                   "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
                 },
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
                   "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
                 }
               ],
@@ -59,11 +59,11 @@
         "options": {
           "repos": [
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
               "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
             },
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
               "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
             }
           ],
diff --git a/test/cases/rhel82_ext4_filesystem.json b/test/cases/rhel82_ext4_filesystem.json
index 8f26c72da..78b0ba78a 100644
--- a/test/cases/rhel82_ext4_filesystem.json
+++ b/test/cases/rhel82_ext4_filesystem.json
@@ -28,11 +28,11 @@
             "options": {
               "repos": [
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
                   "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
                 },
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
                   "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
                 }
               ],
@@ -65,11 +65,11 @@
         "options": {
           "repos": [
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
               "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
             },
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
               "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
             }
           ],
diff --git a/test/cases/rhel82_groups.json b/test/cases/rhel82_groups.json
index 82c51f918..7da3cfc1c 100644
--- a/test/cases/rhel82_groups.json
+++ b/test/cases/rhel82_groups.json
@@ -33,11 +33,11 @@
             "options": {
               "repos": [
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
                   "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
                 },
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
                   "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
                 }
               ],
@@ -70,11 +70,11 @@
         "options": {
           "repos": [
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
               "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
             },
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
               "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
             }
           ],
diff --git a/test/cases/rhel82_openstack.json b/test/cases/rhel82_openstack.json
index c53ce76c4..fd98025c0 100644
--- a/test/cases/rhel82_openstack.json
+++ b/test/cases/rhel82_openstack.json
@@ -22,11 +22,11 @@
             "options": {
               "repos": [
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
                   "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
                 },
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
                   "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
                 }
               ],
@@ -59,11 +59,11 @@
         "options": {
           "repos": [
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
               "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
             },
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
               "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
             }
           ],
diff --git a/test/cases/rhel82_partitioned_disk.json b/test/cases/rhel82_partitioned_disk.json
index 73b46d1af..15c9fe36e 100644
--- a/test/cases/rhel82_partitioned_disk.json
+++ b/test/cases/rhel82_partitioned_disk.json
@@ -28,11 +28,11 @@
             "options": {
               "repos": [
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
                   "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
                 },
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
                   "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
                 }
               ],
@@ -65,11 +65,11 @@
         "options": {
           "repos": [
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
               "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
             },
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
               "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
             }
           ],
diff --git a/test/cases/rhel82_qcow2.json b/test/cases/rhel82_qcow2.json
index bcc0fed68..2d5ae496f 100644
--- a/test/cases/rhel82_qcow2.json
+++ b/test/cases/rhel82_qcow2.json
@@ -22,11 +22,11 @@
             "options": {
               "repos": [
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
                   "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
                 },
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
                   "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
                 }
               ],
@@ -59,11 +59,11 @@
         "options": {
           "repos": [
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
               "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
             },
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
               "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
             }
           ],
diff --git a/test/cases/rhel82_tar.json b/test/cases/rhel82_tar.json
index ab39afe7f..e4de26071 100644
--- a/test/cases/rhel82_tar.json
+++ b/test/cases/rhel82_tar.json
@@ -28,11 +28,11 @@
             "options": {
               "repos": [
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
                   "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
                 },
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
                   "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
                 }
               ],
@@ -65,11 +65,11 @@
         "options": {
           "repos": [
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
               "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
             },
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
               "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
             }
           ],
diff --git a/test/cases/rhel82_vhd.json b/test/cases/rhel82_vhd.json
index 10d8f1c30..79052397b 100644
--- a/test/cases/rhel82_vhd.json
+++ b/test/cases/rhel82_vhd.json
@@ -22,11 +22,11 @@
             "options": {
               "repos": [
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
                   "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
                 },
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
                   "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
                 }
               ],
@@ -59,11 +59,11 @@
         "options": {
           "repos": [
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
               "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
             },
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
               "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
             }
           ],
diff --git a/test/cases/rhel82_vmdk.json b/test/cases/rhel82_vmdk.json
index 8b3ab5316..0427706ec 100644
--- a/test/cases/rhel82_vmdk.json
+++ b/test/cases/rhel82_vmdk.json
@@ -31,11 +31,11 @@
             "options": {
               "repos": [
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
                   "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
                 },
                 {
-                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+                  "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
                   "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
                 }
               ],
@@ -68,11 +68,11 @@
         "options": {
           "repos": [
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/BaseOS/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/BaseOS/x86_64/os",
               "checksum": "sha256:3ce6f4ffa7d05b81fec2ba5a1c4917b351231c3433f2f5eed86ed5f8f97168a1"
             },
             {
-              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/x86_64/os",
+              "baseurl": "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/latest-RHEL-8.2.0/compose/AppStream/x86_64/os",
               "checksum": "sha256:662e1dc0e465dd5e8efec9f42ef24ac9fd8418294254c3c98ea0342d6e903aed"
             }
           ],