Repository: osbuild/osbuild-composer
Pull request #1151 (2021-01-08 11:27:03)

image-types: Updated RHEL-8 KVM and EC2 images information
Updated RHEL-8 KVM and EC2 images information.

Updates in KVM image type:
- Updated the intended use of this image type, based on discussion with Virt PM.
- Added note about the most common way to consume official Red Hat content using this image type.

Updates in EC2 image type:
- Added note about the most common way to consume official Red Hat content using this image type.


Patch:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 2d8cc400e..e9449839a 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,13 +1,28 @@
-# Description
 
+This pull request includes:
 
+- [ ] adequate testing for the new functionality or fixed issue
+- [ ] adequate documentation informing people about the change
 
-**Downstream testing notes:**
+<!--
+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.
+
+For user-visible changes, "adequate documentation" is an entry describing the
+change for users in docs/news. Please refer to docs/news/README.md for details.
+
+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`
-
-For more info see [test/README.md](../test/README.md#downstream-testing-notes)!
+-->
diff --git a/docs/news/README.md b/docs/news/README.md
new file mode 100644
index 000000000..38ba26fff
--- /dev/null
+++ b/docs/news/README.md
@@ -0,0 +1,12 @@
+# Release Notes
+
+This directory contains release notes for _osbuild-composer_ in the form of
+short markdown documents, sorted into subdirectories by release.
+
+When proposing a user-visible change, please add a release note into the
+`unreleased` directory. The note should explain the change from the perspective
+of somebody using _osbuild-composer_ and ideally how the change affects them or
+how they can make use of new functionality.
+
+When preparing a release, a maintainer will rename the `unreleased` directory
+and summarize its contents in `NEWS.md`.
diff --git a/image-types/rhel8/amazon-ec2.md b/image-types/rhel8/amazon-ec2.md
index 5eeb2f77d..d3e2ecbac 100644
--- a/image-types/rhel8/amazon-ec2.md
+++ b/image-types/rhel8/amazon-ec2.md
@@ -23,7 +23,16 @@ package depends on it.
 `dracut-config-rescue` is excluded from the image, because the boot loader
 entry it provides is broken.
 
+The most common way to consume official Red Hat content using this type of
+image is via [Red Hat Update Infrastructure (RHUI)][rhui]. RHUI is used mostly
+for Red Hat Enterprise Linux (RHEL) content, including some of its extensions
+(e.g. for SAP). To access any more specific Red Hat content, one has to use
+[Red Hat Subscription Management (RHSM)][rhsm] ([Red Hat Satellite][satellite]
+/ Red Hat CDN).
 
 [ec2]: https://aws.amazon.com/ec2
 [guidelines]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/building-shared-amis.html
 [requirements]: https://docs.aws.amazon.com/vm-import/latest/userguide/vmie_prereqs.html
+[rhui]: https://access.redhat.com/products/red-hat-update-infrastructure/
+[rhsm]: https://access.redhat.com/products/red-hat-subscription-management
+[satellite]: https://access.redhat.com/products/red-hat-satellite/
diff --git a/image-types/rhel8/kvm-guest-image.md b/image-types/rhel8/kvm-guest-image.md
index 9ed46c8e8..eb0ac9425 100644
--- a/image-types/rhel8/kvm-guest-image.md
+++ b/image-types/rhel8/kvm-guest-image.md
@@ -1,7 +1,10 @@
 # KVM Guest Image
 
-This is an image meant to be used as a generic base image for various
-virtualization environments.
+This is an image meant to be used as a generic base image for KVM
+based hypervisors. It is mainly used with [Red Hat Virtualization
+(RHV)][rhv] or [Red Hat OpenStack Platform (RHOSP)][rhosp]. Using this
+type of image with other types of hypervisors or with cloud providers
+is not supported.
 
 To be usable in common environments, it is provided in the *qcow2* format and
 has *cloud-init* installed and enabled.
@@ -16,3 +19,12 @@ The environments this image is meant to be used in usually don't require any
 specialized firmware. Thus, in order to keep the resulting image size small,
 all `-firmware` packages are excluded. An exception is the `linux-firmware`
 package, which cannot be excluded because the `kernel` package depends on it.
+
+The most common way to consume official Red Hat content using this type of
+image is via [Red Hat Subscription Management (RHSM)][rhsm] ([Red Hat
+Satellite][satellite] / Red Hat CDN).
+
+[rhv]: https://access.redhat.com/products/red-hat-virtualization/
+[rhosp]: https://access.redhat.com/products/red-hat-openstack-platform/
+[rhsm]: https://access.redhat.com/products/red-hat-subscription-management
+[satellite]: https://access.redhat.com/products/red-hat-satellite/
diff --git a/internal/distro/rhel84/distro.go b/internal/distro/rhel84/distro.go
index 575b15b5a..6b2bff5c7 100644
--- a/internal/distro/rhel84/distro.go
+++ b/internal/distro/rhel84/distro.go
@@ -256,8 +256,8 @@ func (t *imageType) pipeline(c *blueprint.Customizations, options distro.ImageOp
 		}
 
 		p.AddStage(osbuild.NewKernelCmdlineStage(&osbuild.KernelCmdlineStageOptions{
-			RootFsUUID: rootPartition.UUID,
-			KernelOpts: "net.ifnames=0 crashkernel=auto",
+			RootFsUUID: rootPartition.Filesystem.UUID,
+			KernelOpts: t.kernelOptions,
 		}))
 	}
 
@@ -295,6 +295,8 @@ func (t *imageType) pipeline(c *blueprint.Customizations, options distro.ImageOp
 
 	if timezone != nil {
 		p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *timezone}))
+	} else {
+		p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: "America/New_York"}))
 	}
 
 	if len(ntpServers) > 0 {
diff --git a/internal/mocks/rpmmd/fixtures.go b/internal/mocks/rpmmd/fixtures.go
index edd07e993..9f1fc2bc9 100644
--- a/internal/mocks/rpmmd/fixtures.go
+++ b/internal/mocks/rpmmd/fixtures.go
@@ -5,14 +5,14 @@ import (
 	"sort"
 	"time"
 
-	"github.com/osbuild/osbuild-composer/internal/jobqueue/testjobqueue"
+	"github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue"
 	"github.com/osbuild/osbuild-composer/internal/worker"
 
 	"github.com/osbuild/osbuild-composer/internal/rpmmd"
 	"github.com/osbuild/osbuild-composer/internal/store"
 )
 
-type FixtureGenerator func() Fixture
+type FixtureGenerator func(tmpdir string) Fixture
 
 func generatePackageList() rpmmd.PackageList {
 	baseTime, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
@@ -52,8 +52,12 @@ func generatePackageList() rpmmd.PackageList {
 	return packageList
 }
 
-func createBaseWorkersFixture() *worker.Server {
-	return worker.NewServer(nil, testjobqueue.New(), "")
+func createBaseWorkersFixture(tmpdir string) *worker.Server {
+	q, err := fsjobqueue.New(tmpdir)
+	if err != nil {
+		panic(err)
+	}
+	return worker.NewServer(nil, q, "")
 }
 
 func createBaseDepsolveFixture() []rpmmd.PackageSpec {
@@ -82,7 +86,7 @@ func createBaseDepsolveFixture() []rpmmd.PackageSpec {
 	}
 }
 
-func BaseFixture() Fixture {
+func BaseFixture(tmpdir string) Fixture {
 	return Fixture{
 		fetchPackageList{
 			generatePackageList(),
@@ -95,11 +99,11 @@ func BaseFixture() Fixture {
 			nil,
 		},
 		store.FixtureBase(),
-		createBaseWorkersFixture(),
+		createBaseWorkersFixture(tmpdir),
 	}
 }
 
-func NoComposesFixture() Fixture {
+func NoComposesFixture(tmpdir string) Fixture {
 	return Fixture{
 		fetchPackageList{
 			generatePackageList(),
@@ -112,11 +116,11 @@ func NoComposesFixture() Fixture {
 			nil,
 		},
 		store.FixtureEmpty(),
-		createBaseWorkersFixture(),
+		createBaseWorkersFixture(tmpdir),
 	}
 }
 
-func NonExistingPackage() Fixture {
+func NonExistingPackage(tmpdir string) Fixture {
 	return Fixture{
 		fetchPackageList{
 			generatePackageList(),
@@ -132,11 +136,11 @@ func NonExistingPackage() Fixture {
 			},
 		},
 		store.FixtureBase(),
-		createBaseWorkersFixture(),
+		createBaseWorkersFixture(tmpdir),
 	}
 }
 
-func BadDepsolve() Fixture {
+func BadDepsolve(tmpdir string) Fixture {
 	return Fixture{
 		fetchPackageList{
 			generatePackageList(),
@@ -152,11 +156,11 @@ func BadDepsolve() Fixture {
 			},
 		},
 		store.FixtureBase(),
-		createBaseWorkersFixture(),
+		createBaseWorkersFixture(tmpdir),
 	}
 }
 
-func BadFetch() Fixture {
+func BadFetch(tmpdir string) Fixture {
 	return Fixture{
 		fetchPackageList{
 			ret:       nil,
@@ -175,6 +179,6 @@ func BadFetch() Fixture {
 			},
 		},
 		store.FixtureBase(),
-		createBaseWorkersFixture(),
+		createBaseWorkersFixture(tmpdir),
 	}
 }
diff --git a/tools/deploy-qemu b/tools/deploy-qemu
index 7866337a3..301ce45f6 100755
--- a/tools/deploy-qemu
+++ b/tools/deploy-qemu
@@ -17,7 +17,7 @@
 
 set -euo pipefail
 
-if [[ -z "$1" || -z "$2" ]]; then
+if [[ "$#" != 2 ]]; then
   echo "usage: $0 IMAGE USERDATA"
   exit 1
 fi


Test patch:
diff --git a/cmd/osbuild-image-tests/constants/constants.go b/cmd/osbuild-image-tests/constants/constants.go
index 2892c408b..0e54bbf92 100644
--- a/cmd/osbuild-image-tests/constants/constants.go
+++ b/cmd/osbuild-image-tests/constants/constants.go
@@ -31,7 +31,7 @@ var TestPaths = struct {
 }{
 	ImageInfo:               "/usr/libexec/osbuild-composer-test/image-info",
 	PrivateKey:              "/usr/share/tests/osbuild-composer/keyring/id_rsa",
-	TestCasesDirectory:      "/usr/share/tests/osbuild-composer/cases",
+	TestCasesDirectory:      "/usr/share/tests/osbuild-composer/manifests",
 	UserData:                "/usr/share/tests/osbuild-composer/cloud-init/user-data",
 	MetaData:                "/usr/share/tests/osbuild-composer/cloud-init/meta-data",
 	AzureDeploymentTemplate: "/usr/share/tests/osbuild-composer/azure/deployment-template.json",
diff --git a/internal/client/unit_test.go b/internal/client/unit_test.go
index 90d6a89a8..2b947bd12 100644
--- a/internal/client/unit_test.go
+++ b/internal/client/unit_test.go
@@ -11,6 +11,7 @@ import (
 	"net"
 	"net/http"
 	"os"
+	"path"
 	"testing"
 
 	"github.com/osbuild/osbuild-composer/internal/distro/fedoratest"
@@ -37,7 +38,11 @@ func executeTests(m *testing.M) int {
 	}
 
 	// Create a mock API server listening on the temporary socket
-	fixture := rpmmd_mock.BaseFixture()
+	err = os.Mkdir(path.Join(tmpdir, "/jobs"), 0755)
+	if err != nil {
+		panic(err)
+	}
+	fixture := rpmmd_mock.BaseFixture(path.Join(tmpdir, "/jobs"))
 	rpm := rpmmd_mock.NewRPMMDMock(fixture)
 	distro := fedoratest.New()
 	arch, err := distro.GetArch("x86_64")
diff --git a/internal/jobqueue/testjobqueue/testjobqueue.go b/internal/jobqueue/testjobqueue/testjobqueue.go
deleted file mode 100644
index 25d8ba323..000000000
--- a/internal/jobqueue/testjobqueue/testjobqueue.go
+++ /dev/null
@@ -1,175 +0,0 @@
-// Package testjobqueue implements jobqueue interface. It is meant for testing,
-// and as such doesn't implement two invariants of jobqueue: it is not safe for
-// concurrent access and `Dequeue()` doesn't wait for new jobs to appear.
-package testjobqueue
-
-import (
-	"context"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"time"
-
-	"github.com/google/uuid"
-	"github.com/osbuild/osbuild-composer/internal/jobqueue"
-)
-
-type testJobQueue struct {
-	jobs map[uuid.UUID]*job
-
-	pending map[string][]uuid.UUID
-
-	// Maps job ids to the jobs that depend on it
-	dependants map[uuid.UUID][]uuid.UUID
-}
-
-type job struct {
-	Id           uuid.UUID
-	Type         string
-	Args         json.RawMessage
-	Dependencies []uuid.UUID
-	Result       json.RawMessage
-	QueuedAt     time.Time
-	StartedAt    time.Time
-	FinishedAt   time.Time
-	Canceled     bool
-}
-
-func New() *testJobQueue {
-	return &testJobQueue{
-		jobs:       make(map[uuid.UUID]*job),
-		pending:    make(map[string][]uuid.UUID),
-		dependants: make(map[uuid.UUID][]uuid.UUID),
-	}
-}
-
-func (q *testJobQueue) Enqueue(jobType string, args interface{}, dependencies []uuid.UUID) (uuid.UUID, error) {
-	var j = job{
-		Id:           uuid.New(),
-		Type:         jobType,
-		Dependencies: dependencies,
-		QueuedAt:     time.Now(),
-	}
-
-	var err error
-	j.Args, err = json.Marshal(args)
-	if err != nil {
-		return uuid.Nil, err
-	}
-
-	q.jobs[j.Id] = &j
-
-	// Verify dependencies and check how many of them are already finished.
-	finished, err := q.countFinishedJobs(j.Dependencies)
-	if err != nil {
-		return uuid.Nil, err
-	}
-
-	// If all dependencies have finished, or there are none, queue the job.
-	// Otherwise, update dependants so that this check is done again when
-	// FinishJob() is called for a dependency.
-	if finished == len(j.Dependencies) {
-		q.pending[j.Type] = append(q.pending[j.Type], j.Id)
-	} else {
-		for _, id := range j.Dependencies {
-			q.dependants[id] = append(q.dependants[id], j.Id)
-		}
-	}
-
-	return j.Id, nil
-}
-
-func (q *testJobQueue) Dequeue(ctx context.Context, jobTypes []string) (uuid.UUID, []uuid.UUID, string, json.RawMessage, error) {
-	for _, t := range jobTypes {
-		if len(q.pending[t]) == 0 {
-			continue
-		}
-
-		id := q.pending[t][0]
-		q.pending[t] = q.pending[t][1:]
-
-		j := q.jobs[id]
-
-		j.StartedAt = time.Now()
-		return j.Id, j.Dependencies, j.Type, j.Args, nil
-	}
-
-	return uuid.Nil, nil, "", nil, errors.New("no job available")
-}
-
-func (q *testJobQueue) FinishJob(id uuid.UUID, result interface{}) error {
-	j, exists := q.jobs[id]
-	if !exists {
-		return jobqueue.ErrNotExist
-	}
-
-	if j.StartedAt.IsZero() || !j.FinishedAt.IsZero() {
-		return jobqueue.ErrNotRunning
-	}
-
-	var err error
-	j.Result, err = json.Marshal(result)
-	if err != nil {
-		return fmt.Errorf("error marshaling result: %v", err)
-	}
-
-	j.FinishedAt = time.Now()
-
-	for _, depid := range q.dependants[id] {
-		dep := q.jobs[depid]
-		n, err := q.countFinishedJobs(dep.Dependencies)
-		if err != nil {
-			return err
-		}
-		if n == len(dep.Dependencies) {
-			q.pending[dep.Type] = append(q.pending[dep.Type], dep.Id)
-		}
-	}
-	delete(q.dependants, id)
-
-	return nil
-}
-
-func (q *testJobQueue) CancelJob(id uuid.UUID) error {
-	j, exists := q.jobs[id]
-	if !exists {
-		return jobqueue.ErrNotExist
-	}
-
-	j.Canceled = true
-
-	return nil
-}
-
-func (q *testJobQueue) JobStatus(id uuid.UUID) (result json.RawMessage, queued, started, finished time.Time, canceled bool, deps []uuid.UUID, err error) {
-	j, exists := q.jobs[id]
-	if !exists {
-		err = jobqueue.ErrNotExist
-		return
-	}
-
-	result = j.Result
-	queued = j.QueuedAt
-	started = j.StartedAt
-	finished = j.FinishedAt
-	canceled = j.Canceled
-	deps = j.Dependencies
-
-	return
-}
-
-// Returns the number of finished jobs in `ids`.
-func (q *testJobQueue) countFinishedJobs(ids []uuid.UUID) (int, error) {
-	n := 0
-	for _, id := range ids {
-		j, exists := q.jobs[id]
-		if !exists {
-			return 0, jobqueue.ErrNotExist
-		}
-		if j.Result != nil {
-			n += 1
-		}
-	}
-
-	return n, nil
-}
diff --git a/internal/kojiapi/server_test.go b/internal/kojiapi/server_test.go
index 76c1bf99d..1f5ac9c07 100644
--- a/internal/kojiapi/server_test.go
+++ b/internal/kojiapi/server_test.go
@@ -12,7 +12,6 @@ import (
 	"sync"
 	"testing"
 
-	"github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue"
 	"github.com/osbuild/osbuild-composer/internal/kojiapi"
 	"github.com/osbuild/osbuild-composer/internal/kojiapi/api"
 	distro_mock "github.com/osbuild/osbuild-composer/internal/mocks/distro"
@@ -24,7 +23,7 @@ import (
 )
 
 func newTestKojiServer(t *testing.T, dir string) (*kojiapi.Server, *worker.Server) {
-	rpm_fixture := rpmmd_mock.BaseFixture()
+	rpm_fixture := rpmmd_mock.BaseFixture(dir)
 	rpm := rpmmd_mock.NewRPMMDMock(rpm_fixture)
 	require.NotNil(t, rpm)
 
@@ -32,16 +31,10 @@ func newTestKojiServer(t *testing.T, dir string) (*kojiapi.Server, *worker.Serve
 	require.NoError(t, err)
 	require.NotNil(t, distros)
 
-	queue, err := fsjobqueue.New(dir)
-	require.NoError(t, err)
-
-	workerServer := worker.NewServer(nil, queue, "")
-	require.NotNil(t, workerServer)
-
-	kojiServer := kojiapi.NewServer(nil, workerServer, rpm, distros)
+	kojiServer := kojiapi.NewServer(nil, rpm_fixture.Workers, rpm, distros)
 	require.NotNil(t, kojiServer)
 
-	return kojiServer, workerServer
+	return kojiServer, rpm_fixture.Workers
 }
 
 func TestStatus(t *testing.T) {
diff --git a/internal/weldr/api_test.go b/internal/weldr/api_test.go
index 86bd19470..9f32dec3e 100644
--- a/internal/weldr/api_test.go
+++ b/internal/weldr/api_test.go
@@ -4,6 +4,7 @@ import (
 	"archive/tar"
 	"bytes"
 	"io"
+	"io/ioutil"
 	"math/rand"
 	"net/http"
 	"net/http/httptest"
@@ -27,8 +28,8 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func createWeldrAPI(fixtureGenerator rpmmd_mock.FixtureGenerator) (*API, *store.Store) {
-	fixture := fixtureGenerator()
+func createWeldrAPI(tempdir string, fixtureGenerator rpmmd_mock.FixtureGenerator) (*API, *store.Store) {
+	fixture := fixtureGenerator(tempdir)
 	rpm := rpmmd_mock.NewRPMMDMock(fixture)
 	repos := []rpmmd.RepoConfig{{Name: "test-id", BaseURL: "http://example.com/test/os/x86_64", CheckGPG: true}}
 	d := test_distro.New()
@@ -61,8 +62,12 @@ func TestBasic(t *testing.T) {
 		{"/api/v0/blueprints/info/foo", http.StatusOK, `{"blueprints":[],"changes":[],"errors":[{"id":"UnknownBlueprint","msg":"foo: "}]}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		test.TestRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
 	}
 }
@@ -82,13 +87,21 @@ func TestBlueprintsNew(t *testing.T) {
 		{"POST", "/api/v0/blueprints/new", ``, http.StatusBadRequest, `{"status":false,"errors":[{"id":"BlueprintsError","msg":"Missing blueprint"}]}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		test.TestRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
 	}
 }
 
 func TestBlueprintsNewToml(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	blueprint := `
 name = "test"
 description = "Test"
@@ -102,7 +115,7 @@ version = "2.4.*"`
 	req.Header.Set("Content-Type", "text/x-toml")
 	recorder := httptest.NewRecorder()
 
-	api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+	api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 	api.ServeHTTP(recorder, req)
 
 	r := recorder.Result()
@@ -110,11 +123,15 @@ version = "2.4.*"`
 }
 
 func TestBlueprintsEmptyToml(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	req := httptest.NewRequest("POST", "/api/v0/blueprints/new", bytes.NewReader(nil))
 	req.Header.Set("Content-Type", "text/x-toml")
 	recorder := httptest.NewRecorder()
 
-	api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+	api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 	api.ServeHTTP(recorder, req)
 
 	r := recorder.Result()
@@ -122,6 +139,10 @@ func TestBlueprintsEmptyToml(t *testing.T) {
 }
 
 func TestBlueprintsInvalidToml(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	blueprint := `
 name = "test"
 description = "Test"
@@ -135,7 +156,7 @@ version = "2.4.*"`
 	req.Header.Set("Content-Type", "text/x-toml")
 	recorder := httptest.NewRecorder()
 
-	api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+	api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 	api.ServeHTTP(recorder, req)
 
 	r := recorder.Result()
@@ -155,13 +176,21 @@ func TestBlueprintsWorkspaceJSON(t *testing.T) {
 		{"POST", "/api/v0/blueprints/workspace", ``, http.StatusBadRequest, `{"status":false,"errors":[{"id":"BlueprintsError","msg":"Missing blueprint"}]}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		test.TestRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
 	}
 }
 
 func TestBlueprintsWorkspaceTOML(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	blueprint := `
 name = "test"
 description = "Test"
@@ -175,7 +204,7 @@ version = "2.4.*"`
 	req.Header.Set("Content-Type", "text/x-toml")
 	recorder := httptest.NewRecorder()
 
-	api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+	api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 	api.ServeHTTP(recorder, req)
 
 	r := recorder.Result()
@@ -183,11 +212,15 @@ version = "2.4.*"`
 }
 
 func TestBlueprintsWorkspaceEmptyTOML(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	req := httptest.NewRequest("POST", "/api/v0/blueprints/workspace", bytes.NewReader(nil))
 	req.Header.Set("Content-Type", "text/x-toml")
 	recorder := httptest.NewRecorder()
 
-	api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+	api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 	api.ServeHTTP(recorder, req)
 
 	r := recorder.Result()
@@ -195,6 +228,10 @@ func TestBlueprintsWorkspaceEmptyTOML(t *testing.T) {
 }
 
 func TestBlueprintsWorkspaceInvalidTOML(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	blueprint := `
 name = "test"
 description = "Test"
@@ -208,7 +245,7 @@ version = "2.4.*"`
 	req.Header.Set("Content-Type", "text/x-toml")
 	recorder := httptest.NewRecorder()
 
-	api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+	api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 	api.ServeHTTP(recorder, req)
 
 	r := recorder.Result()
@@ -230,8 +267,12 @@ func TestBlueprintsInfo(t *testing.T) {
 		{"GET", "/api/v0/blueprints/info/test3-non", ``, http.StatusOK, `{"blueprints":[],"changes":[],"errors":[{"id":"UnknownBlueprint","msg":"test3-non: "}]}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test1","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
 		test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test2","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
 		test.SendHTTP(api, true, "POST", "/api/v0/blueprints/workspace", `{"name":"test2","description":"Test","packages":[{"name":"systemd","version":"123"}],"version":"0.0.0"}`)
@@ -242,7 +283,11 @@ func TestBlueprintsInfo(t *testing.T) {
 }
 
 func TestBlueprintsInfoToml(t *testing.T) {
-	api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
+	api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 	test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test1","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
 
 	req := httptest.NewRequest("GET", "/api/v0/blueprints/info/test1?format=toml", nil)
@@ -253,7 +298,7 @@ func TestBlueprintsInfoToml(t *testing.T) {
 	require.Equal(t, http.StatusOK, resp.StatusCode)
 
 	var got blueprint.Blueprint
-	_, err := toml.DecodeReader(resp.Body, &got)
+	_, err = toml.DecodeReader(resp.Body, &got)
 	require.NoErrorf(t, err, "error decoding toml file")
 
 	expected := blueprint.Blueprint{
@@ -272,7 +317,11 @@ func TestBlueprintsInfoToml(t *testing.T) {
 }
 
 func TestNonExistentBlueprintsInfoToml(t *testing.T) {
-	api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
+	api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 	req := httptest.NewRequest("GET", "/api/v0/blueprints/info/test3-non?format=toml", nil)
 	recorder := httptest.NewRecorder()
 	api.ServeHTTP(recorder, req)
@@ -337,8 +386,12 @@ func TestBlueprintsFreeze(t *testing.T) {
 			{rpmmd_mock.BaseFixture, "/api/v0/blueprints/freeze/test,test2", http.StatusOK, `{"blueprints":[{"blueprint":{"name":"test","description":"Test","version":"0.0.1","packages":[{"name":"dep-package1","version":"1.33-2.fc30.x86_64"},{"name":"dep-package3","version":"7:3.0.3-1.fc30.x86_64"}],"modules":[{"name":"dep-package2","version":"2.9-1.fc30.x86_64"}],"groups":[]}},{"blueprint":{"name":"test2","description":"Test","version":"0.0.0","packages":[{"name":"dep-package1","version":"1.33-2.fc30.x86_64"},{"name":"dep-package3","version":"7:3.0.3-1.fc30.x86_64"}],"modules":[{"name":"dep-package2","version":"2.9-1.fc30.x86_64"}],"groups":[]}}],"errors":[]}`},
 		}
 
+		tempdir, err := ioutil.TempDir("", "weldr-tests-")
+		require.NoError(t, err)
+		defer os.RemoveAll(tempdir)
+
 		for _, c := range cases {
-			api, _ := createWeldrAPI(c.Fixture)
+			api, _ := createWeldrAPI(tempdir, c.Fixture)
 			test.SendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"dep-package1","version":"*"},{"name":"dep-package3","version":"*"}], "modules":[{"name":"dep-package2","version":"*"}],"version":"0.0.0"}`)
 			test.SendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test2","description":"Test","packages":[{"name":"dep-package1","version":"*"},{"name":"dep-package3","version":"*"}], "modules":[{"name":"dep-package2","version":"*"}],"version":"0.0.0"}`)
 			test.TestRoute(t, api, false, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
@@ -356,8 +409,12 @@ func TestBlueprintsFreeze(t *testing.T) {
 			{rpmmd_mock.BaseFixture, "/api/v0/blueprints/freeze/missing?format=toml", http.StatusOK, ""},
 		}
 
+		tempdir, err := ioutil.TempDir("", "weldr-tests-")
+		require.NoError(t, err)
+		defer os.RemoveAll(tempdir)
+
 		for _, c := range cases {
-			api, _ := createWeldrAPI(c.Fixture)
+			api, _ := createWeldrAPI(tempdir, c.Fixture)
 			test.SendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"dep-package1","version":"*"},{"name":"dep-package3","version":"*"}], "modules":[{"name":"dep-package2","version":"*"}],"version":"0.0.0"}`)
 			test.TestTOMLRoute(t, api, false, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedTOML)
 		}
@@ -373,8 +430,12 @@ func TestBlueprintsFreeze(t *testing.T) {
 			{rpmmd_mock.BaseFixture, "/api/v0/blueprints/freeze/test,test2?format=toml", http.StatusBadRequest, "{\"status\":false,\"errors\":[{\"id\":\"HTTPError\",\"msg\":\"toml format only supported when requesting one blueprint\"}]}"},
 		}
 
+		tempdir, err := ioutil.TempDir("", "weldr-tests-")
+		require.NoError(t, err)
+		defer os.RemoveAll(tempdir)
+
 		for _, c := range cases {
-			api, _ := createWeldrAPI(c.Fixture)
+			api, _ := createWeldrAPI(tempdir, c.Fixture)
 			test.SendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"dep-package1","version":"*"},{"name":"dep-package3","version":"*"}], "modules":[{"name":"dep-package2","version":"*"}],"version":"0.0.0"}`)
 			test.SendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test2","description":"Test","packages":[{"name":"dep-package1","version":"*"},{"name":"dep-package3","version":"*"}], "modules":[{"name":"dep-package2","version":"*"}],"version":"0.0.0"}`)
 			test.TestRoute(t, api, false, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
@@ -393,8 +454,12 @@ func TestBlueprintsDiff(t *testing.T) {
 		{"GET", "/api/v0/blueprints/diff/test/NEWEST/WORKSPACE", ``, http.StatusOK, `{"diff":[{"new":{"Package":{"name":"systemd","version":"123"}},"old":null},{"new":null,"old":{"Package":{"name":"httpd","version":"2.4.*"}}}]}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
 		test.SendHTTP(api, true, "POST", "/api/v0/blueprints/workspace", `{"name":"test","description":"Test","packages":[{"name":"systemd","version":"123"}],"version":"0.0.0"}`)
 		test.TestRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
@@ -414,8 +479,12 @@ func TestBlueprintsDelete(t *testing.T) {
 		{"DELETE", "/api/v0/blueprints/delete/test3-non", ``, http.StatusBadRequest, `{"status":false,"errors":[{"id":"BlueprintsError","msg":"Unknown blueprint: test3-non"}]}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		test.SendHTTP(api, true, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"httpd","version":"2.4.*"}],"version":"0.0.0"}`)
 		test.TestRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
 		test.SendHTTP(api, true, "DELETE", "/api/v0/blueprints/delete/test", ``)
@@ -423,7 +492,11 @@ func TestBlueprintsDelete(t *testing.T) {
 }
 
 func TestBlueprintsChanges(t *testing.T) {
-	api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
+	api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 	rand.Seed(time.Now().UnixNano())
 	id := strconv.Itoa(rand.Int())
 	ignoreFields := []string{"commit", "timestamp"}
@@ -448,8 +521,12 @@ func TestBlueprintsDepsolve(t *testing.T) {
 		{rpmmd_mock.BadDepsolve, http.StatusOK, `{"blueprints":[{"blueprint":{"name":"test","description":"Test","version":"0.0.1","packages":[{"name":"dep-package1","version":"*"}],"groups":[],"modules":[{"name":"dep-package3","version":"*"}]},"dependencies":[]}],"errors":[{"id":"BlueprintsError","msg":"test: DNF error occured: DepsolveError: There was a problem depsolving ['go2rpm']: \n Problem: conflicting requests\n  - nothing provides askalono-cli needed by go2rpm-1-4.fc31.noarch"}]}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(c.Fixture)
+		api, _ := createWeldrAPI(tempdir, c.Fixture)
 		test.SendHTTP(api, false, "POST", "/api/v0/blueprints/new", `{"name":"test","description":"Test","packages":[{"name":"dep-package1","version":"*"}],"modules":[{"name":"dep-package3","version":"*"}],"version":"0.0.0"}`)
 		test.TestRoute(t, api, false, "GET", "/api/v0/blueprints/depsolve/test", ``, c.ExpectedStatus, c.ExpectedJSON)
 		test.SendHTTP(api, false, "DELETE", "/api/v0/blueprints/delete/test", ``)
@@ -524,8 +601,12 @@ func TestCompose(t *testing.T) {
 		{false, "POST", "/api/v1/compose", `{"blueprint_name": "test","compose_type":"qcow2","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"}},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, s := createWeldrAPI(rpmmd_mock.NoComposesFixture)
+		api, s := createWeldrAPI(tempdir, rpmmd_mock.NoComposesFixture)
 		test.TestRoute(t, api, c.External, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON, c.IgnoreFields...)
 
 		if c.ExpectedStatus != http.StatusOK {
@@ -568,8 +649,12 @@ func TestComposeDelete(t *testing.T) {
 		{"/api/v0/compose/delete/30000000-0000-0000-0000-000000000003,42000000-0000-0000-0000-000000000000", `{"uuids":[{"uuid":"30000000-0000-0000-0000-000000000003","status":true}],"errors":[{"id":"UnknownUUID","msg":"compose 42000000-0000-0000-0000-000000000000 doesn't exist"}]}`, []string{"30000000-0000-0000-0000-000000000000", "30000000-0000-0000-0000-000000000001", "30000000-0000-0000-0000-000000000002"}},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, s := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, s := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		test.TestRoute(t, api, false, "DELETE", c.Path, "", http.StatusOK, c.ExpectedJSON)
 
 		idsInStore := []string{}
@@ -603,8 +688,12 @@ func TestComposeStatus(t *testing.T) {
 		t.Skip("This test is for internal testing only")
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		test.TestRoute(t, api, false, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON, "id", "job_created", "job_started")
 	}
 }
@@ -628,8 +717,12 @@ func TestComposeInfo(t *testing.T) {
 		t.Skip("This test is for internal testing only")
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		test.TestRoute(t, api, false, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
 	}
 }
@@ -654,8 +747,12 @@ func TestComposeLogs(t *testing.T) {
 		{"/api/v1/compose/results/30000000-0000-0000-0000-000000000002", "attachment; filename=30000000-0000-0000-0000-000000000002.tar", "application/x-tar", "30000000-0000-0000-0000-000000000002.json", "{\"sources\":{},\"pipeline\":{}}"},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range successCases {
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 
 		response := test.SendHTTP(api, false, "GET", c.Path, "")
 		require.Equalf(t, http.StatusOK, response.StatusCode, "%s: unexpected status code", c.Path)
@@ -694,7 +791,7 @@ func TestComposeLogs(t *testing.T) {
 	}
 
 	for _, c := range failureCases {
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		test.TestRoute(t, api, false, "GET", c.Path, "", http.StatusBadRequest, c.ExpectedJSON)
 	}
 }
@@ -719,8 +816,12 @@ func TestComposeLog(t *testing.T) {
 		t.Skip("This test is for internal testing only")
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		test.TestNonJsonRoute(t, api, false, "GET", c.Path, "", c.ExpectedStatus, c.ExpectedResponse)
 	}
 }
@@ -743,8 +844,12 @@ func TestComposeQueue(t *testing.T) {
 		t.Skip("This test is for internal testing only")
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(c.Fixture)
+		api, _ := createWeldrAPI(tempdir, c.Fixture)
 		test.TestRoute(t, api, false, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON, "id", "job_created", "job_started")
 	}
 }
@@ -767,8 +872,12 @@ func TestComposeFinished(t *testing.T) {
 		t.Skip("This test is for internal testing only")
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(c.Fixture)
+		api, _ := createWeldrAPI(tempdir, c.Fixture)
 		test.TestRoute(t, api, false, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON, "id", "job_created", "job_started")
 	}
 }
@@ -791,8 +900,12 @@ func TestComposeFailed(t *testing.T) {
 		t.Skip("This test is for internal testing only")
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(c.Fixture)
+		api, _ := createWeldrAPI(tempdir, c.Fixture)
 		test.TestRoute(t, api, false, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON, "id", "job_created", "job_started")
 	}
 }
@@ -814,14 +927,22 @@ func TestSourcesNew(t *testing.T) {
 		{"POST", "/api/v0/projects/source/new", `{"name": "fish", "url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","check_ssl": false,"check_gpg": false}`, http.StatusBadRequest, `{"errors": [{"id": "ProjectsError","msg": "Problem parsing POST body: 'type' field is missing from request"}],"status":false}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		test.TestRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
 		test.SendHTTP(api, true, "DELETE", "/api/v0/projects/source/delete/fish", ``)
 	}
 }
 
 func TestSourcesNewToml(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	source := `
 name = "fish"
 url = "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/"
@@ -833,7 +954,7 @@ check_gpg = false
 	req.Header.Set("Content-Type", "text/x-toml")
 	recorder := httptest.NewRecorder()
 
-	api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+	api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 	api.ServeHTTP(recorder, req)
 
 	r := recorder.Result()
@@ -844,6 +965,10 @@ check_gpg = false
 
 // Empty TOML, and invalid TOML should return an error
 func TestSourcesNewWrongToml(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	sources := []string{``, `
 [fish]
 name = "fish"
@@ -857,7 +982,7 @@ check_gpg = false
 		req.Header.Set("Content-Type", "text/x-toml")
 		recorder := httptest.NewRecorder()
 
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		api.ServeHTTP(recorder, req)
 
 		r := recorder.Result()
@@ -866,9 +991,13 @@ check_gpg = false
 }
 
 func TestSourcesInfo(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	sourceStr := `{"name":"fish","type":"yum-baseurl","url":"https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","check_gpg":false,"check_ssl":false,"system":false}`
 
-	api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+	api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 	test.SendHTTP(api, true, "POST", "/api/v0/projects/source/new", sourceStr)
 	test.TestRoute(t, api, true, "GET", "/api/v0/projects/source/info/fish", ``, 200, `{"sources":{"fish":`+sourceStr+`},"errors":[]}`)
 	test.TestRoute(t, api, true, "GET", "/api/v0/projects/source/info/fish?format=json", ``, 200, `{"sources":{"fish":`+sourceStr+`},"errors":[]}`)
@@ -876,9 +1005,13 @@ func TestSourcesInfo(t *testing.T) {
 }
 
 func TestSourcesInfoToml(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	sourceStr := `{"name":"fish","type":"yum-baseurl","url":"https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","check_gpg":false,"check_ssl":false,"system":false}`
 
-	api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+	api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 	test.SendHTTP(api, true, "POST", "/api/v0/projects/source/new", sourceStr)
 
 	req := httptest.NewRequest("GET", "/api/v0/projects/source/info/fish?format=toml", nil)
@@ -887,7 +1020,7 @@ func TestSourcesInfoToml(t *testing.T) {
 	resp := recorder.Result()
 
 	var sources map[string]store.SourceConfig
-	_, err := toml.DecodeReader(resp.Body, &sources)
+	_, err = toml.DecodeReader(resp.Body, &sources)
 	require.NoErrorf(t, err, "error decoding toml file")
 
 	expected := map[string]store.SourceConfig{
@@ -913,8 +1046,12 @@ func TestSourcesDelete(t *testing.T) {
 		{"DELETE", "/api/v0/projects/source/delete/fish", ``, http.StatusOK, `{"status":true}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
+		api, _ := createWeldrAPI(tempdir, rpmmd_mock.BaseFixture)
 		test.SendHTTP(api, true, "POST", "/api/v0/projects/source/new", `{"name": "fish","url": "https://download.opensuse.org/repositories/shells:/fish:/release:/3/Fedora_29/","type": "yum-baseurl","check_ssl": false,"check_gpg": false}`)
 		test.TestRoute(t, api, true, c.Method, c.Path, c.Body, c.ExpectedStatus, c.ExpectedJSON)
 		test.SendHTTP(api, true, "DELETE", "/api/v0/projects/source/delete/fish", ``)
@@ -933,8 +1070,12 @@ func TestProjectsDepsolve(t *testing.T) {
 		{rpmmd_mock.BadDepsolve, "/api/v0/projects/depsolve/go2rpm", http.StatusBadRequest, `{"status":false,"errors":[{"id":"PROJECTS_ERROR","msg":"BadRequest: DNF error occured: DepsolveError: There was a problem depsolving ['go2rpm']: \n Problem: conflicting requests\n  - nothing provides askalono-cli needed by go2rpm-1-4.fc31.noarch"}]}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(c.Fixture)
+		api, _ := createWeldrAPI(tempdir, c.Fixture)
 		test.TestRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
 	}
 }
@@ -954,8 +1095,12 @@ func TestProjectsInfo(t *testing.T) {
 		{rpmmd_mock.BadFetch, "/api/v0/projects/info/package2*,package16", http.StatusBadRequest, `{"status":false,"errors":[{"id":"ModulesError","msg":"msg: DNF error occured: FetchError: There was a problem when fetching packages."}]}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(c.Fixture)
+		api, _ := createWeldrAPI(tempdir, c.Fixture)
 		test.TestRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
 	}
 }
@@ -976,8 +1121,12 @@ func TestModulesInfo(t *testing.T) {
 		{rpmmd_mock.BadFetch, "/api/v0/modules/info/package2*,package16", http.StatusBadRequest, `{"status":false,"errors":[{"id":"ModulesError","msg":"msg: DNF error occured: FetchError: There was a problem when fetching packages."}]}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(c.Fixture)
+		api, _ := createWeldrAPI(tempdir, c.Fixture)
 		test.TestRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
 	}
 }
@@ -995,8 +1144,12 @@ func TestProjectsList(t *testing.T) {
 		{rpmmd_mock.BaseFixture, "/api/v0/projects/list?offset=1&limit=1", http.StatusOK, `{"total":22,"offset":1,"limit":1,"projects":[{"name":"package1","summary":"pkg1 sum","description":"pkg1 desc","homepage":"https://pkg1.example.com","upstream_vcs":"UPSTREAM_VCS","builds":[{"arch":"x86_64","build_time":"2006-02-02T15:04:05","epoch":0,"release":"1.fc30","changelog":"CHANGELOG_NEEDED","build_config_ref":"BUILD_CONFIG_REF","build_env_ref":"BUILD_ENV_REF","metadata":{},"source":{"license":"MIT","version":"1.0","source_ref":"SOURCE_REF","metadata":{}}},{"arch":"x86_64","build_time":"2006-02-03T15:04:05","epoch":0,"release":"1.fc30","changelog":"CHANGELOG_NEEDED","build_config_ref":"BUILD_CONFIG_REF","build_env_ref":"BUILD_ENV_REF","metadata":{},"source":{"license":"MIT","version":"1.1","source_ref":"SOURCE_REF","metadata":{}}}]}]}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(c.Fixture)
+		api, _ := createWeldrAPI(tempdir, c.Fixture)
 		test.TestRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
 	}
 }
@@ -1017,8 +1170,12 @@ func TestModulesList(t *testing.T) {
 		{rpmmd_mock.BaseFixture, "/api/v0/modules/list/*", http.StatusOK, `{"total":22,"offset":0,"limit":20,"modules":[{"name":"package0","group_type":"rpm"},{"name":"package1","group_type":"rpm"},{"name":"package10","group_type":"rpm"},{"name":"package11","group_type":"rpm"},{"name":"package12","group_type":"rpm"},{"name":"package13","group_type":"rpm"},{"name":"package14","group_type":"rpm"},{"name":"package15","group_type":"rpm"},{"name":"package16","group_type":"rpm"},{"name":"package17","group_type":"rpm"},{"name":"package18","group_type":"rpm"},{"name":"package19","group_type":"rpm"},{"name":"package2","group_type":"rpm"},{"name":"package20","group_type":"rpm"},{"name":"package21","group_type":"rpm"},{"name":"package3","group_type":"rpm"},{"name":"package4","group_type":"rpm"},{"name":"package5","group_type":"rpm"},{"name":"package6","group_type":"rpm"},{"name":"package7","group_type":"rpm"}]}`},
 	}
 
+	tempdir, err := ioutil.TempDir("", "weldr-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		api, _ := createWeldrAPI(c.Fixture)
+		api, _ := createWeldrAPI(tempdir, c.Fixture)
 		test.TestRoute(t, api, true, "GET", c.Path, ``, c.ExpectedStatus, c.ExpectedJSON)
 	}
 }
diff --git a/internal/worker/server_test.go b/internal/worker/server_test.go
index 277bd4d85..7a34eb1dd 100644
--- a/internal/worker/server_test.go
+++ b/internal/worker/server_test.go
@@ -3,21 +3,35 @@ package worker_test
 import (
 	"context"
 	"fmt"
+	"io/ioutil"
 	"net/http"
+	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 
 	"github.com/osbuild/osbuild-composer/internal/distro"
 	"github.com/osbuild/osbuild-composer/internal/distro/fedoratest"
-	"github.com/osbuild/osbuild-composer/internal/jobqueue/testjobqueue"
+	"github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue"
 	"github.com/osbuild/osbuild-composer/internal/test"
 	"github.com/osbuild/osbuild-composer/internal/worker"
 )
 
+func newTestServer(t *testing.T, tempdir string) *worker.Server {
+	q, err := fsjobqueue.New(tempdir)
+	if err != nil {
+		t.Fatalf("error creating fsjobqueue: %v", err)
+	}
+	return worker.NewServer(nil, q, "")
+}
+
 // Ensure that the status request returns OK.
 func TestStatus(t *testing.T) {
-	server := worker.NewServer(nil, testjobqueue.New(), "")
+	tempdir, err := ioutil.TempDir("", "worker-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
+	server := newTestServer(t, tempdir)
 	handler := server.Handler()
 	test.TestRoute(t, handler, false, "GET", "/api/worker/v1/status", ``, http.StatusOK, `{"status":"OK"}`, "message")
 }
@@ -43,14 +57,22 @@ func TestErrors(t *testing.T) {
 		{"PATCH", "/api/worker/v1/jobs/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", `{"status":"FINISHED"}`, http.StatusNotFound},
 	}
 
+	tempdir, err := ioutil.TempDir("", "worker-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	for _, c := range cases {
-		server := worker.NewServer(nil, testjobqueue.New(), "")
+		server := newTestServer(t, tempdir)
 		handler := server.Handler()
 		test.TestRoute(t, handler, false, c.Method, c.Path, c.Body, c.ExpectedStatus, "{}", "message")
 	}
 }
 
 func TestCreate(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "worker-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	distroStruct := fedoratest.New()
 	arch, err := distroStruct.GetArch("x86_64")
 	if err != nil {
@@ -64,7 +86,7 @@ func TestCreate(t *testing.T) {
 	if err != nil {
 		t.Fatalf("error creating osbuild manifest")
 	}
-	server := worker.NewServer(nil, testjobqueue.New(), "")
+	server := newTestServer(t, tempdir)
 	handler := server.Handler()
 
 	_, err = server.EnqueueOSBuild(arch.Name(), &worker.OSBuildJob{Manifest: manifest})
@@ -75,6 +97,10 @@ func TestCreate(t *testing.T) {
 }
 
 func TestCancel(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "worker-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	distroStruct := fedoratest.New()
 	arch, err := distroStruct.GetArch("x86_64")
 	if err != nil {
@@ -88,7 +114,7 @@ func TestCancel(t *testing.T) {
 	if err != nil {
 		t.Fatalf("error creating osbuild manifest")
 	}
-	server := worker.NewServer(nil, testjobqueue.New(), "")
+	server := newTestServer(t, tempdir)
 	handler := server.Handler()
 
 	jobId, err := server.EnqueueOSBuild(arch.Name(), &worker.OSBuildJob{Manifest: manifest})
@@ -112,6 +138,10 @@ func TestCancel(t *testing.T) {
 }
 
 func TestUpdate(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "worker-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	distroStruct := fedoratest.New()
 	arch, err := distroStruct.GetArch("x86_64")
 	if err != nil {
@@ -125,7 +155,7 @@ func TestUpdate(t *testing.T) {
 	if err != nil {
 		t.Fatalf("error creating osbuild manifest")
 	}
-	server := worker.NewServer(nil, testjobqueue.New(), "")
+	server := newTestServer(t, tempdir)
 	handler := server.Handler()
 
 	jobId, err := server.EnqueueOSBuild(arch.Name(), &worker.OSBuildJob{Manifest: manifest})
@@ -143,6 +173,10 @@ func TestUpdate(t *testing.T) {
 }
 
 func TestUpload(t *testing.T) {
+	tempdir, err := ioutil.TempDir("", "worker-tests-")
+	require.NoError(t, err)
+	defer os.RemoveAll(tempdir)
+
 	distroStruct := fedoratest.New()
 	arch, err := distroStruct.GetArch("x86_64")
 	if err != nil {
@@ -156,7 +190,7 @@ func TestUpload(t *testing.T) {
 	if err != nil {
 		t.Fatalf("error creating osbuild manifest")
 	}
-	server := worker.NewServer(nil, testjobqueue.New(), "")
+	server := newTestServer(t, tempdir)
 	handler := server.Handler()
 
 	jobID, err := server.EnqueueOSBuild(arch.Name(), &worker.OSBuildJob{Manifest: manifest})
diff --git a/test/README.md b/test/README.md
index 6e3be9ec9..d04571cea 100644
--- a/test/README.md
+++ b/test/README.md
@@ -60,7 +60,7 @@ tests, see `.github/workflows/tests.yml`.
 
 ## Image tests
 
-In the `test/data/cases` directory, sample image builds and their tests are
+In the `test/data/manifests` directory, sample image builds and their tests are
 collected for the various distros, architectures, configuration we support.
 
 Each test case describes how the image is built, the expected osbuild
diff --git a/test/data/manifests/rhel_84-x86_64-ami-boot.json b/test/data/manifests/rhel_84-x86_64-ami-boot.json
index ff1861825..ca7299b54 100644
--- a/test/data/manifests/rhel_84-x86_64-ami-boot.json
+++ b/test/data/manifests/rhel_84-x86_64-ami-boot.json
@@ -3097,6 +3097,12 @@
             "language": "en_US"
           }
         },
+        {
+          "name": "org.osbuild.timezone",
+          "options": {
+            "zone": "America/New_York"
+          }
+        },
         {
           "name": "org.osbuild.selinux",
           "options": {
@@ -9216,6 +9222,6 @@
       "tuned.service",
       "unbound-anchor.timer"
     ],
-    "timezone": "UTC"
+    "timezone": "New_York"
   }
 }
diff --git a/test/data/manifests/rhel_84-x86_64-openstack-boot.json b/test/data/manifests/rhel_84-x86_64-openstack-boot.json
index e9be656f4..7b527baec 100644
--- a/test/data/manifests/rhel_84-x86_64-openstack-boot.json
+++ b/test/data/manifests/rhel_84-x86_64-openstack-boot.json
@@ -3336,6 +3336,12 @@
             "language": "en_US"
           }
         },
+        {
+          "name": "org.osbuild.timezone",
+          "options": {
+            "zone": "America/New_York"
+          }
+        },
         {
           "name": "org.osbuild.users",
           "options": {
@@ -9844,6 +9850,6 @@
       "tuned.service",
       "unbound-anchor.timer"
     ],
-    "timezone": "UTC"
+    "timezone": "New_York"
   }
 }
diff --git a/test/data/manifests/rhel_84-x86_64-qcow2-boot.json b/test/data/manifests/rhel_84-x86_64-qcow2-boot.json
index a3f228af8..99afd65e4 100644
--- a/test/data/manifests/rhel_84-x86_64-qcow2-boot.json
+++ b/test/data/manifests/rhel_84-x86_64-qcow2-boot.json
@@ -3465,6 +3465,12 @@
             "language": "en_US"
           }
         },
+        {
+          "name": "org.osbuild.timezone",
+          "options": {
+            "zone": "America/New_York"
+          }
+        },
         {
           "name": "org.osbuild.users",
           "options": {
@@ -10217,6 +10223,6 @@
       "tuned.service",
       "unbound-anchor.timer"
     ],
-    "timezone": "UTC"
+    "timezone": "New_York"
   }
 }
diff --git a/test/data/manifests/rhel_84-x86_64-vhd-boot.json b/test/data/manifests/rhel_84-x86_64-vhd-boot.json
index a394cb7e1..e83c26863 100644
--- a/test/data/manifests/rhel_84-x86_64-vhd-boot.json
+++ b/test/data/manifests/rhel_84-x86_64-vhd-boot.json
@@ -3291,6 +3291,12 @@
             "language": "en_US"
           }
         },
+        {
+          "name": "org.osbuild.timezone",
+          "options": {
+            "zone": "America/New_York"
+          }
+        },
         {
           "name": "org.osbuild.users",
           "options": {
@@ -9754,6 +9760,6 @@
       "unbound-anchor.timer",
       "waagent.service"
     ],
-    "timezone": "UTC"
+    "timezone": "New_York"
   }
 }
diff --git a/test/data/manifests/rhel_84-x86_64-vmdk-boot.json b/test/data/manifests/rhel_84-x86_64-vmdk-boot.json
index d22078710..da56828c7 100644
--- a/test/data/manifests/rhel_84-x86_64-vmdk-boot.json
+++ b/test/data/manifests/rhel_84-x86_64-vmdk-boot.json
@@ -3153,6 +3153,12 @@
             "language": "en_US"
           }
         },
+        {
+          "name": "org.osbuild.timezone",
+          "options": {
+            "zone": "America/New_York"
+          }
+        },
         {
           "name": "org.osbuild.users",
           "options": {
@@ -9363,6 +9369,6 @@
       "vgauthd.service",
       "vmtoolsd.service"
     ],
-    "timezone": "UTC"
+    "timezone": "New_York"
   }
 }