Repository: osbuild/osbuild-composer
Pull request #184 (2019-12-20 20:26:38)

Allow for user defined image size
When starting a compose users can pass in an image size. The resulting image will then be of this size. If no image size is passed a default size will be used. 


Patch:
diff --git a/cmd/osbuild-pipeline/main.go b/cmd/osbuild-pipeline/main.go
index a5d0191da..370c41eb8 100644
--- a/cmd/osbuild-pipeline/main.go
+++ b/cmd/osbuild-pipeline/main.go
@@ -58,7 +58,7 @@ func main() {
 		panic(err.Error())
 	}
 
-	pipeline, err := d.Pipeline(blueprint, nil, checksums, archArg, format)
+	pipeline, err := d.Pipeline(blueprint, nil, checksums, archArg, format, 0)
 	if err != nil {
 		panic(err.Error())
 	}
diff --git a/internal/distro/distro.go b/internal/distro/distro.go
index d68541cdd..6e134276b 100644
--- a/internal/distro/distro.go
+++ b/internal/distro/distro.go
@@ -34,7 +34,7 @@ type Distro interface {
 	// Returns an osbuild pipeline that generates an image in the given
 	// output format with all packages and customizations specified in the
 	// given blueprint.
-	Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.RepoConfig, checksums map[string]string, outputArchitecture, outputFormat string) (*pipeline.Pipeline, error)
+	Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.RepoConfig, checksums map[string]string, outputArchitecture, outputFormat string, size uint64) (*pipeline.Pipeline, error)
 
 	// Returns a osbuild runner that can be used on this distro.
 	Runner() string
diff --git a/internal/distro/fedora30/distro.go b/internal/distro/fedora30/distro.go
index 99c990795..34a65a21a 100644
--- a/internal/distro/fedora30/distro.go
+++ b/internal/distro/fedora30/distro.go
@@ -36,12 +36,15 @@ type output struct {
 	DisabledServices []string
 	KernelOptions    string
 	Bootable         bool
-	Assembler        func(uefi bool) *pipeline.Assembler
+	DefaultSize      uint64
+	Assembler        func(uefi bool, size uint64) *pipeline.Assembler
 }
 
 const Name = "fedora-30"
 
 func New(confPaths []string) *Fedora30 {
+	const GigaByte = 1024 * 1024 * 1024
+
 	r := Fedora30{
 		arches:  map[string]arch{},
 		outputs: map[string]output{},
@@ -110,7 +113,10 @@ func New(confPaths []string) *Fedora30 {
 		},
 		KernelOptions: "ro no_timer_check console=ttyS0,115200n8 console=tty1 biosdevname=0 net.ifnames=0 console=ttyS0,115200",
 		Bootable:      true,
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("raw.xz", "image.raw.xz", uefi) },
+		DefaultSize:   6 * GigaByte,
+		Assembler: func(uefi bool, size uint64) *pipeline.Assembler {
+			return r.qemuAssembler("raw.xz", "image.raw.xz", uefi, size)
+		},
 	}
 
 	r.outputs["ext4-filesystem"] = output{
@@ -129,7 +135,8 @@ func New(confPaths []string) *Fedora30 {
 		},
 		KernelOptions: "ro biosdevname=0 net.ifnames=0",
 		Bootable:      false,
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.rawFSAssembler("filesystem.img") },
+		DefaultSize:   2 * GigaByte,
+		Assembler:     func(uefi bool, size uint64) *pipeline.Assembler { return r.rawFSAssembler("filesystem.img", size) },
 	}
 
 	r.outputs["partitioned-disk"] = output{
@@ -148,7 +155,10 @@ func New(confPaths []string) *Fedora30 {
 		},
 		KernelOptions: "ro biosdevname=0 net.ifnames=0",
 		Bootable:      true,
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("raw", "disk.img", uefi) },
+		DefaultSize:   2 * GigaByte,
+		Assembler: func(uefi bool, size uint64) *pipeline.Assembler {
+			return r.qemuAssembler("raw", "disk.img", uefi, size)
+		},
 	}
 
 	r.outputs["qcow2"] = output{
@@ -172,7 +182,10 @@ func New(confPaths []string) *Fedora30 {
 		},
 		KernelOptions: "ro biosdevname=0 net.ifnames=0",
 		Bootable:      true,
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("qcow2", "disk.qcow2", uefi) },
+		DefaultSize:   2 * GigaByte,
+		Assembler: func(uefi bool, size uint64) *pipeline.Assembler {
+			return r.qemuAssembler("qcow2", "disk.qcow2", uefi, size)
+		},
 	}
 
 	r.outputs["openstack"] = output{
@@ -195,7 +208,10 @@ func New(confPaths []string) *Fedora30 {
 		},
 		KernelOptions: "ro biosdevname=0 net.ifnames=0",
 		Bootable:      true,
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("qcow2", "disk.qcow2", uefi) },
+		DefaultSize:   2 * GigaByte,
+		Assembler: func(uefi bool, size uint64) *pipeline.Assembler {
+			return r.qemuAssembler("qcow2", "disk.qcow2", uefi, size)
+		},
 	}
 
 	r.outputs["tar"] = output{
@@ -214,7 +230,8 @@ func New(confPaths []string) *Fedora30 {
 		},
 		KernelOptions: "ro biosdevname=0 net.ifnames=0",
 		Bootable:      false,
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.tarAssembler("root.tar.xz", "xz") },
+		DefaultSize:   2 * GigaByte,
+		Assembler:     func(uefi bool, size uint64) *pipeline.Assembler { return r.tarAssembler("root.tar.xz", "xz") },
 	}
 
 	r.outputs["vhd"] = output{
@@ -248,7 +265,10 @@ func New(confPaths []string) *Fedora30 {
 		// These kernel parameters are required by Azure documentation
 		KernelOptions: "ro biosdevname=0 rootdelay=300 console=ttyS0 earlyprintk=ttyS0 net.ifnames=0",
 		Bootable:      true,
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("vpc", "disk.vhd", uefi) },
+		DefaultSize:   2 * GigaByte,
+		Assembler: func(uefi bool, size uint64) *pipeline.Assembler {
+			return r.qemuAssembler("vpc", "disk.vhd", uefi, size)
+		},
 	}
 
 	r.outputs["vmdk"] = output{
@@ -268,7 +288,10 @@ func New(confPaths []string) *Fedora30 {
 		},
 		KernelOptions: "ro biosdevname=0 net.ifnames=0",
 		Bootable:      true,
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("vmdk", "disk.vmdk", uefi) },
+		DefaultSize:   2 * GigaByte,
+		Assembler: func(uefi bool, size uint64) *pipeline.Assembler {
+			return r.qemuAssembler("vmdk", "disk.vmdk", uefi, size)
+		},
 	}
 
 	return &r
@@ -298,7 +321,7 @@ func (r *Fedora30) FilenameFromType(outputFormat string) (string, string, error)
 	return "", "", errors.New("invalid output format: " + outputFormat)
 }
 
-func (r *Fedora30) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.RepoConfig, checksums map[string]string, outputArchitecture, outputFormat string) (*pipeline.Pipeline, error) {
+func (r *Fedora30) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.RepoConfig, checksums map[string]string, outputArchitecture, outputFormat string, size uint64) (*pipeline.Pipeline, error) {
 	output, exists := r.outputs[outputFormat]
 	if !exists {
 		return nil, errors.New("invalid output format: " + outputFormat)
@@ -373,7 +396,11 @@ func (r *Fedora30) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.Repo
 	}
 
 	p.AddStage(pipeline.NewSELinuxStage(r.selinuxStageOptions()))
-	p.Assembler = output.Assembler(arch.UEFI)
+
+	if size == 0 {
+		size = output.DefaultSize
+	}
+	p.Assembler = output.Assembler(arch.UEFI, size)
 
 	return p, nil
 }
@@ -552,14 +579,14 @@ func (r *Fedora30) selinuxStageOptions() *pipeline.SELinuxStageOptions {
 	}
 }
 
-func (r *Fedora30) qemuAssembler(format string, filename string, uefi bool) *pipeline.Assembler {
+func (r *Fedora30) qemuAssembler(format string, filename string, uefi bool, size uint64) *pipeline.Assembler {
 	var options pipeline.QEMUAssemblerOptions
 	if uefi {
 		fstype := uuid.MustParse("C12A7328-F81F-11D2-BA4B-00A0C93EC93B")
 		options = pipeline.QEMUAssemblerOptions{
 			Format:   format,
 			Filename: filename,
-			Size:     3222274048,
+			Size:     size,
 			PTUUID:   "8DFDFF87-C96E-EA48-A3A6-9408F1F6B1EF",
 			PTType:   "gpt",
 			Partitions: []pipeline.QEMUPartition{
@@ -588,7 +615,7 @@ func (r *Fedora30) qemuAssembler(format string, filename string, uefi bool) *pip
 		options = pipeline.QEMUAssemblerOptions{
 			Format:   format,
 			Filename: filename,
-			Size:     3222274048,
+			Size:     size,
 			PTUUID:   "0x14fc63d2",
 			PTType:   "mbr",
 			Partitions: []pipeline.QEMUPartition{
@@ -615,7 +642,7 @@ func (r *Fedora30) tarAssembler(filename, compression string) *pipeline.Assemble
 		})
 }
 
-func (r *Fedora30) rawFSAssembler(filename string) *pipeline.Assembler {
+func (r *Fedora30) rawFSAssembler(filename string, size uint64) *pipeline.Assembler {
 	id, err := uuid.Parse("76a22bf4-f153-4541-b6c7-0332c0dfaeac")
 	if err != nil {
 		panic("invalid UUID")
@@ -624,6 +651,6 @@ func (r *Fedora30) rawFSAssembler(filename string) *pipeline.Assembler {
 		&pipeline.RawFSAssemblerOptions{
 			Filename:           filename,
 			RootFilesystemUUDI: id,
-			Size:               3222274048,
+			Size:               size,
 		})
 }
diff --git a/internal/distro/rhel82/distro.go b/internal/distro/rhel82/distro.go
index 8845b3722..d387a1298 100644
--- a/internal/distro/rhel82/distro.go
+++ b/internal/distro/rhel82/distro.go
@@ -37,7 +37,8 @@ type output struct {
 	Bootable         bool
 	DefaultTarget    string
 	KernelOptions    string
-	Assembler        func(uefi bool) *pipeline.Assembler
+	DefaultSize      uint64
+	Assembler        func(uefi bool, size uint64) *pipeline.Assembler
 }
 
 const Name = "rhel-8.2"
@@ -161,8 +162,9 @@ func New(confPaths []string) *RHEL82 {
 		DefaultTarget: "multi-user.target",
 		Bootable:      true,
 		KernelOptions: "ro console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto",
-		Assembler: func(uefi bool) *pipeline.Assembler {
-			return r.qemuAssembler("raw.xz", "image.raw.xz", 6*GigaByte, uefi)
+		DefaultSize:   6 * GigaByte,
+		Assembler: func(uefi bool, size uint64) *pipeline.Assembler {
+			return r.qemuAssembler("raw.xz", "image.raw.xz", uefi, size)
 		},
 	}
 
@@ -187,7 +189,8 @@ func New(confPaths []string) *RHEL82 {
 		},
 		Bootable:      false,
 		KernelOptions: "ro net.ifnames=0",
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.rawFSAssembler("filesystem.img") },
+		DefaultSize:   2 * GigaByte,
+		Assembler:     func(uefi bool, size uint64) *pipeline.Assembler { return r.rawFSAssembler("filesystem.img", size) },
 	}
 
 	r.outputs["partitioned-disk"] = output{
@@ -211,7 +214,10 @@ func New(confPaths []string) *RHEL82 {
 		},
 		Bootable:      true,
 		KernelOptions: "ro net.ifnames=0",
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("raw", "disk.img", 3*GigaByte, uefi) },
+		DefaultSize:   2 * GigaByte,
+		Assembler: func(uefi bool, size uint64) *pipeline.Assembler {
+			return r.qemuAssembler("raw", "disk.img", uefi, size)
+		},
 	}
 
 	r.outputs["qcow2"] = output{
@@ -292,7 +298,10 @@ func New(confPaths []string) *RHEL82 {
 		},
 		Bootable:      true,
 		KernelOptions: "console=ttyS0 console=ttyS0,115200n8 no_timer_check crashkernel=auto net.ifnames=0",
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("qcow2", "disk.qcow2", 3*GigaByte, uefi) },
+		DefaultSize:   2 * GigaByte,
+		Assembler: func(uefi bool, size uint64) *pipeline.Assembler {
+			return r.qemuAssembler("qcow2", "disk.qcow2", uefi, size)
+		},
 	}
 
 	r.outputs["openstack"] = output{
@@ -319,7 +328,10 @@ func New(confPaths []string) *RHEL82 {
 		},
 		Bootable:      true,
 		KernelOptions: "ro net.ifnames=0",
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("qcow2", "disk.qcow2", 3*GigaByte, uefi) },
+		DefaultSize:   2 * GigaByte,
+		Assembler: func(uefi bool, size uint64) *pipeline.Assembler {
+			return r.qemuAssembler("qcow2", "disk.qcow2", uefi, size)
+		},
 	}
 
 	r.outputs["tar"] = output{
@@ -343,7 +355,7 @@ func New(confPaths []string) *RHEL82 {
 		},
 		Bootable:      false,
 		KernelOptions: "ro net.ifnames=0",
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.tarAssembler("root.tar.xz", "xz") },
+		Assembler:     func(uefi bool, size uint64) *pipeline.Assembler { return r.tarAssembler("root.tar.xz", "xz") },
 	}
 
 	r.outputs["vhd"] = output{
@@ -383,7 +395,10 @@ func New(confPaths []string) *RHEL82 {
 		DefaultTarget: "multi-user.target",
 		Bootable:      true,
 		KernelOptions: "ro biosdevname=0 rootdelay=300 console=ttyS0 earlyprintk=ttyS0 net.ifnames=0",
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("vpc", "disk.vhd", 3*GigaByte, uefi) },
+		DefaultSize:   2 * GigaByte,
+		Assembler: func(uefi bool, size uint64) *pipeline.Assembler {
+			return r.qemuAssembler("vpc", "disk.vhd", uefi, size)
+		},
 	}
 
 	r.outputs["vmdk"] = output{
@@ -408,7 +423,10 @@ func New(confPaths []string) *RHEL82 {
 		},
 		Bootable:      true,
 		KernelOptions: "ro net.ifnames=0",
-		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("vmdk", "disk.vmdk", 3*GigaByte, uefi) },
+		DefaultSize:   2 * GigaByte,
+		Assembler: func(uefi bool, size uint64) *pipeline.Assembler {
+			return r.qemuAssembler("vmdk", "disk.vmdk", uefi, size)
+		},
 	}
 
 	return &r
@@ -438,7 +456,7 @@ func (r *RHEL82) FilenameFromType(outputFormat string) (string, string, error) {
 	return "", "", errors.New("invalid output format: " + outputFormat)
 }
 
-func (r *RHEL82) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.RepoConfig, checksums map[string]string, outputArchitecture, outputFormat string) (*pipeline.Pipeline, error) {
+func (r *RHEL82) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.RepoConfig, checksums map[string]string, outputArchitecture, outputFormat string, size uint64) (*pipeline.Pipeline, error) {
 	output, exists := r.outputs[outputFormat]
 	if !exists {
 		return nil, errors.New("invalid output format: " + outputFormat)
@@ -518,7 +536,11 @@ func (r *RHEL82) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.RepoCo
 	}
 
 	p.AddStage(pipeline.NewSELinuxStage(r.selinuxStageOptions()))
-	p.Assembler = output.Assembler(arch.UEFI)
+
+	if size == 0 {
+		size = output.DefaultSize
+	}
+	p.Assembler = output.Assembler(arch.UEFI, size)
 
 	return p, nil
 }
@@ -696,7 +718,7 @@ func (r *RHEL82) selinuxStageOptions() *pipeline.SELinuxStageOptions {
 	}
 }
 
-func (r *RHEL82) qemuAssembler(format string, filename string, size uint64, uefi bool) *pipeline.Assembler {
+func (r *RHEL82) qemuAssembler(format string, filename string, uefi bool, size uint64) *pipeline.Assembler {
 	var options pipeline.QEMUAssemblerOptions
 	if uefi {
 		fstype := uuid.MustParse("C12A7328-F81F-11D2-BA4B-00A0C93EC93B")
@@ -759,7 +781,7 @@ func (r *RHEL82) tarAssembler(filename, compression string) *pipeline.Assembler
 		})
 }
 
-func (r *RHEL82) rawFSAssembler(filename string) *pipeline.Assembler {
+func (r *RHEL82) rawFSAssembler(filename string, size uint64) *pipeline.Assembler {
 	id, err := uuid.Parse("0bd700f8-090f-4556-b797-b340297ea1bd")
 	if err != nil {
 		panic("invalid UUID")
@@ -768,7 +790,7 @@ func (r *RHEL82) rawFSAssembler(filename string) *pipeline.Assembler {
 		&pipeline.RawFSAssemblerOptions{
 			Filename:           filename,
 			RootFilesystemUUDI: id,
-			Size:               3221225472,
+			Size:               size,
 			FilesystemType:     "xfs",
 		})
 }
diff --git a/internal/pipeline/tar_assembler.go b/internal/pipeline/tar_assembler.go
index c0e79349a..993572e0d 100644
--- a/internal/pipeline/tar_assembler.go
+++ b/internal/pipeline/tar_assembler.go
@@ -6,6 +6,7 @@ package pipeline
 // compression type, and stores the output with the given filename.
 type TarAssemblerOptions struct {
 	Filename    string `json:"filename"`
+	Size        uint64 `json:"size"`
 	Compression string `json:"compression,omitempty"`
 }
 
@@ -13,9 +14,10 @@ func (TarAssemblerOptions) isAssemblerOptions() {}
 
 // NewTarAssemblerOptions creates a new TarAssemblerOptions object, with the
 // mandatory options set.
-func NewTarAssemblerOptions(filename string) *TarAssemblerOptions {
+func NewTarAssemblerOptions(filename string, size uint64) *TarAssemblerOptions {
 	return &TarAssemblerOptions{
 		Filename: filename,
+		Size:     size,
 	}
 }
 
diff --git a/internal/rcm/api.go b/internal/rcm/api.go
index 3ca9cc6b4..2cf4d11a1 100644
--- a/internal/rcm/api.go
+++ b/internal/rcm/api.go
@@ -129,7 +129,7 @@ func (api *API) submit(writer http.ResponseWriter, request *http.Request, _ http
 	// Push the requested compose to the store
 	composeUUID := uuid.New()
 	// nil is used as an upload target, because LocalTarget is already used in the PushCompose function
-	err = api.store.PushCompose(composeUUID, &blueprint.Blueprint{}, make(map[string]string), composeRequest.Architectures[0], composeRequest.ImageTypes[0], nil)
+	err = api.store.PushCompose(composeUUID, &blueprint.Blueprint{}, make(map[string]string), composeRequest.Architectures[0], composeRequest.ImageTypes[0], 0, nil)
 	if err != nil {
 		if api.logger != nil {
 			api.logger.Println("RCM API failed to push compose:", err)
diff --git a/internal/store/store.go b/internal/store/store.go
index d276e9d59..a574d7959 100644
--- a/internal/store/store.go
+++ b/internal/store/store.go
@@ -443,7 +443,7 @@ func (s *Store) GetComposeResult(id uuid.UUID) (io.ReadCloser, error) {
 	return os.Open(*s.stateDir + "/outputs/" + id.String() + "/result.json")
 }
 
-func (s *Store) PushCompose(composeID uuid.UUID, bp *blueprint.Blueprint, checksums map[string]string, arch, composeType string, uploadTarget *target.Target) error {
+func (s *Store) PushCompose(composeID uuid.UUID, bp *blueprint.Blueprint, checksums map[string]string, arch, composeType string, size uint64, uploadTarget *target.Target) error {
 	targets := []*target.Target{}
 
 	if s.stateDir != nil {
@@ -463,7 +463,7 @@ func (s *Store) PushCompose(composeID uuid.UUID, bp *blueprint.Blueprint, checks
 		repos = append(repos, source.RepoConfig())
 	}
 
-	pipeline, err := s.distro.Pipeline(bp, repos, checksums, arch, composeType)
+	pipeline, err := s.distro.Pipeline(bp, repos, checksums, arch, composeType, size)
 	if err != nil {
 		return err
 	}
diff --git a/internal/weldr/api.go b/internal/weldr/api.go
index e587ecc40..18d169cc8 100644
--- a/internal/weldr/api.go
+++ b/internal/weldr/api.go
@@ -1194,6 +1194,7 @@ func (api *API) composeHandler(writer http.ResponseWriter, request *http.Request
 	type ComposeRequest struct {
 		BlueprintName string         `json:"blueprint_name"`
 		ComposeType   string         `json:"compose_type"`
+		Size          uint64         `json:"size"`
 		Branch        string         `json:"branch"`
 		Upload        *UploadRequest `json:"upload"`
 	}
@@ -1201,6 +1202,7 @@ func (api *API) composeHandler(writer http.ResponseWriter, request *http.Request
 		BuildID uuid.UUID `json:"build_id"`
 		Status  bool      `json:"status"`
 	}
+	const MegaByte = 1024 * 1024
 
 	contentType := request.Header["Content-Type"]
 	if len(contentType) != 1 || contentType[0] != "application/json" {
@@ -1246,6 +1248,13 @@ func (api *API) composeHandler(writer http.ResponseWriter, request *http.Request
 
 	bp := api.store.GetBlueprintCommitted(cr.BlueprintName)
 
+	size := cr.Size
+	
+	// Microsoft Azure requires vhd images to be rounded up to the nearest MB
+	if cr.ComposeType == "vhd" && size%MegaByte != 0 {
+		size = (size/MegaByte + 1) * MegaByte
+	}
+
 	if bp != nil {
 		_, checksums, err := api.depsolveBlueprint(bp, true)
 		if err != nil {
@@ -1257,7 +1266,7 @@ func (api *API) composeHandler(writer http.ResponseWriter, request *http.Request
 			return
 		}
 
-		err = api.store.PushCompose(reply.BuildID, bp, checksums, api.arch, cr.ComposeType, uploadTarget)
+		err = api.store.PushCompose(reply.BuildID, bp, checksums, api.arch, cr.ComposeType, size, uploadTarget)
 
 		// TODO: we should probably do some kind of blueprint validation in future
 		// for now, let's just 500 and bail out


Test patch:
diff --git a/internal/distro/distro_test.go b/internal/distro/distro_test.go
index 57deef2b6..9672f15be 100644
--- a/internal/distro/distro_test.go
+++ b/internal/distro/distro_test.go
@@ -48,7 +48,7 @@ func TestDistro_Pipeline(t *testing.T) {
 				t.Errorf("unknown distro: %v", tt.Compose.Distro)
 				return
 			}
-			got, err := d.Pipeline(tt.Compose.Blueprint, nil, tt.Compose.Checksums, tt.Compose.Arch, tt.Compose.OutputFormat)
+			got, err := d.Pipeline(tt.Compose.Blueprint, nil, tt.Compose.Checksums, tt.Compose.Arch, tt.Compose.OutputFormat, 0)
 			if (err != nil) != (tt.Pipeline == nil) {
 				t.Errorf("distro.Pipeline() error = %v", err)
 				return
diff --git a/internal/distro/test/distro.go b/internal/distro/test/distro.go
index 732c2a879..b6f1af18c 100644
--- a/internal/distro/test/distro.go
+++ b/internal/distro/test/distro.go
@@ -42,7 +42,7 @@ func (d *TestDistro) FilenameFromType(outputFormat string) (string, string, erro
 	}
 }
 
-func (d *TestDistro) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.RepoConfig, checksums map[string]string, outputArch, outputFormat string) (*pipeline.Pipeline, error) {
+func (d *TestDistro) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.RepoConfig, checksums map[string]string, outputArch, outputFormat string, size uint64) (*pipeline.Pipeline, error) {
 	if outputFormat == "test_output" && outputArch == "test_arch" {
 		return &pipeline.Pipeline{}, nil
 	} else {
diff --git a/internal/jobqueue/api_test.go b/internal/jobqueue/api_test.go
index b04b61c46..535369028 100644
--- a/internal/jobqueue/api_test.go
+++ b/internal/jobqueue/api_test.go
@@ -45,7 +45,7 @@ func TestCreate(t *testing.T) {
 	store := store.New(nil, distro)
 	api := jobqueue.New(nil, store)
 
-	err := store.PushCompose(id, &blueprint.Blueprint{}, map[string]string{"test-repo": "test:foo"}, "test_arch", "test_output", nil)
+	err := store.PushCompose(id, &blueprint.Blueprint{}, map[string]string{"test-repo": "test:foo"}, "test_arch", "test_output", 0, nil)
 	if err != nil {
 		t.Fatalf("error pushing compose: %v", err)
 	}
@@ -61,7 +61,7 @@ func testUpdateTransition(t *testing.T, from, to string, expectedStatus int) {
 	api := jobqueue.New(nil, store)
 
 	if from != "VOID" {
-		err := store.PushCompose(id, &blueprint.Blueprint{}, map[string]string{"test": "test:foo"}, "test_arch", "test_output", nil)
+		err := store.PushCompose(id, &blueprint.Blueprint{}, map[string]string{"test": "test:foo"}, "test_arch", "test_output", 0, nil)
 		if err != nil {
 			t.Fatalf("error pushing compose: %v", err)
 		}
diff --git a/test/cases/ami_empty_blueprint.json b/test/cases/ami_empty_blueprint.json
index 789be7101..8ca11f267 100644
--- a/test/cases/ami_empty_blueprint.json
+++ b/test/cases/ami_empty_blueprint.json
@@ -130,7 +130,7 @@
       "options": {
         "format": "raw.xz",
         "filename": "image.raw.xz",
-        "size": 3222274048,
+        "size": 6442450944,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
@@ -599,7 +599,7 @@
         "fstype": "ext4",
         "label": null,
         "partuuid": "14fc63d2-01",
-        "size": 3221225472,
+        "size": 6441402368,
         "start": 1048576,
         "type": "83",
         "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac"
diff --git a/test/cases/disk_empty_blueprint.json b/test/cases/disk_empty_blueprint.json
index 77989674b..6014cca1b 100644
--- a/test/cases/disk_empty_blueprint.json
+++ b/test/cases/disk_empty_blueprint.json
@@ -115,7 +115,7 @@
       "options": {
         "format": "raw",
         "filename": "disk.img",
-        "size": 3222274048,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
@@ -549,7 +549,7 @@
         "fstype": "ext4",
         "label": null,
         "partuuid": "14fc63d2-01",
-        "size": 3221225472,
+        "size": 2146435072,
         "start": 1048576,
         "type": "83",
         "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac"
diff --git a/test/cases/disk_local_boot.json b/test/cases/disk_local_boot.json
index 4b1d634cf..ddbea9bf9 100644
--- a/test/cases/disk_local_boot.json
+++ b/test/cases/disk_local_boot.json
@@ -168,7 +168,7 @@
       "options": {
         "format": "raw",
         "filename": "disk.img",
-        "size": 3222274048,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
diff --git a/test/cases/ext4_empty_blueprint.json b/test/cases/ext4_empty_blueprint.json
index 3d67acc6f..a120b987f 100644
--- a/test/cases/ext4_empty_blueprint.json
+++ b/test/cases/ext4_empty_blueprint.json
@@ -99,7 +99,7 @@
       "options": {
         "filename": "filesystem.img",
         "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-        "size": 3222274048
+        "size": 2147483648
       }
     }
   },
diff --git a/test/cases/ext4_local_boot.json b/test/cases/ext4_local_boot.json
index 2465bf64f..2f798a0ac 100644
--- a/test/cases/ext4_local_boot.json
+++ b/test/cases/ext4_local_boot.json
@@ -153,7 +153,7 @@
       "options": {
         "filename": "filesystem.img",
         "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-        "size": 3222274048
+        "size": 2147483648
       }
     }
   }
diff --git a/test/cases/openstack_empty_blueprint.json b/test/cases/openstack_empty_blueprint.json
index 6ad6a163a..c3947bc46 100644
--- a/test/cases/openstack_empty_blueprint.json
+++ b/test/cases/openstack_empty_blueprint.json
@@ -119,7 +119,7 @@
       "options": {
         "format": "qcow2",
         "filename": "disk.qcow2",
-        "size": 3222274048,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
@@ -605,7 +605,7 @@
         "fstype": "ext4",
         "label": null,
         "partuuid": "14fc63d2-01",
-        "size": 3221225472,
+        "size": 2146435072,
         "start": 1048576,
         "type": "83",
         "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac"
diff --git a/test/cases/openstack_local_boot.json b/test/cases/openstack_local_boot.json
index b258740bb..23591101a 100644
--- a/test/cases/openstack_local_boot.json
+++ b/test/cases/openstack_local_boot.json
@@ -169,7 +169,7 @@
       "options": {
         "format": "qcow2",
         "filename": "disk.qcow2",
-        "size": 3222274048,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
diff --git a/test/cases/qcow2_empty_blueprint.json b/test/cases/qcow2_empty_blueprint.json
index a018fccdf..8c27fd5b6 100644
--- a/test/cases/qcow2_empty_blueprint.json
+++ b/test/cases/qcow2_empty_blueprint.json
@@ -120,7 +120,7 @@
       "options": {
         "format": "qcow2",
         "filename": "disk.qcow2",
-        "size": 3222274048,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
@@ -581,7 +581,7 @@
         "fstype": "ext4",
         "label": null,
         "partuuid": "14fc63d2-01",
-        "size": 3221225472,
+        "size": 2146435072,
         "start": 1048576,
         "type": "83",
         "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac"
diff --git a/test/cases/qcow2_local_boot.json b/test/cases/qcow2_local_boot.json
index 109fe6cb8..bd73fe313 100644
--- a/test/cases/qcow2_local_boot.json
+++ b/test/cases/qcow2_local_boot.json
@@ -170,7 +170,7 @@
       "options": {
         "format": "qcow2",
         "filename": "disk.qcow2",
-        "size": 3222274048,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
diff --git a/test/cases/rhel82_ext4_filesystem.json b/test/cases/rhel82_ext4_filesystem.json
index 78b0ba78a..fa93db76b 100644
--- a/test/cases/rhel82_ext4_filesystem.json
+++ b/test/cases/rhel82_ext4_filesystem.json
@@ -131,7 +131,7 @@
       "options": {
         "filename": "filesystem.img",
         "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-        "size": 3221225472,
+        "size": 2147483648,
         "fs_type": "xfs"
       }
     }
diff --git a/test/cases/rhel82_openstack.json b/test/cases/rhel82_openstack.json
index fd98025c0..31acfb21d 100644
--- a/test/cases/rhel82_openstack.json
+++ b/test/cases/rhel82_openstack.json
@@ -129,7 +129,7 @@
       "options": {
         "format": "qcow2",
         "filename": "disk.qcow2",
-        "size": 3221225472,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
diff --git a/test/cases/rhel82_partitioned_disk.json b/test/cases/rhel82_partitioned_disk.json
index 15c9fe36e..59ede4d56 100644
--- a/test/cases/rhel82_partitioned_disk.json
+++ b/test/cases/rhel82_partitioned_disk.json
@@ -146,7 +146,7 @@
         "format": "raw",
         "filename": "disk.img",
         "ptuuid": "0x14fc63d2",
-        "size": 3221225472,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
diff --git a/test/cases/rhel82_qcow2.json b/test/cases/rhel82_qcow2.json
index 2d5ae496f..efd6b1c82 100644
--- a/test/cases/rhel82_qcow2.json
+++ b/test/cases/rhel82_qcow2.json
@@ -185,7 +185,7 @@
       "options": {
         "format": "qcow2",
         "filename": "disk.qcow2",
-        "size": 3221225472,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
diff --git a/test/cases/rhel82_vhd.json b/test/cases/rhel82_vhd.json
index 79052397b..c98c132bd 100644
--- a/test/cases/rhel82_vhd.json
+++ b/test/cases/rhel82_vhd.json
@@ -144,7 +144,7 @@
       "options": {
         "format": "vpc",
         "filename": "disk.vhd",
-        "size": 3221225472,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
diff --git a/test/cases/rhel82_vmdk.json b/test/cases/rhel82_vmdk.json
index 0427706ec..e678cff0c 100644
--- a/test/cases/rhel82_vmdk.json
+++ b/test/cases/rhel82_vmdk.json
@@ -149,7 +149,7 @@
       "options": {
         "format": "vmdk",
         "filename": "disk.vmdk",
-        "size": 3221225472,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
diff --git a/test/cases/vhd_empty_blueprint.json b/test/cases/vhd_empty_blueprint.json
index 4f3879316..c0613e13f 100644
--- a/test/cases/vhd_empty_blueprint.json
+++ b/test/cases/vhd_empty_blueprint.json
@@ -134,7 +134,7 @@
       "options": {
         "format": "vpc",
         "filename": "disk.vhd",
-        "size": 3222274048,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
@@ -578,7 +578,7 @@
         "fstype": "ext4",
         "label": null,
         "partuuid": "14fc63d2-01",
-        "size": 3221225472,
+        "size": 2146435072,
         "start": 1048576,
         "type": "83",
         "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac"
diff --git a/test/cases/vhd_local_boot.json b/test/cases/vhd_local_boot.json
index c5d10bfda..0fc1a6c3b 100644
--- a/test/cases/vhd_local_boot.json
+++ b/test/cases/vhd_local_boot.json
@@ -168,7 +168,7 @@
       "options": {
         "format": "qcow2",
         "filename": "disk.vhd",
-        "size": 3222274048,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
diff --git a/test/cases/vmdk_empty_blueprint.json b/test/cases/vmdk_empty_blueprint.json
index 747956fd1..eb08689cc 100644
--- a/test/cases/vmdk_empty_blueprint.json
+++ b/test/cases/vmdk_empty_blueprint.json
@@ -116,7 +116,7 @@
       "options": {
         "format": "vmdk",
         "filename": "disk.vmdk",
-        "size": 3222274048,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [
@@ -566,7 +566,7 @@
         "fstype": "ext4",
         "label": null,
         "partuuid": "14fc63d2-01",
-        "size": 3221225472,
+        "size": 2146435072,
         "start": 1048576,
         "type": "83",
         "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac"
diff --git a/test/cases/vmdk_local_boot.json b/test/cases/vmdk_local_boot.json
index 01cb02cfc..db0d34377 100644
--- a/test/cases/vmdk_local_boot.json
+++ b/test/cases/vmdk_local_boot.json
@@ -166,7 +166,7 @@
       "options": {
         "format": "vmdk",
         "filename": "disk.vmdk",
-        "size": 3222274048,
+        "size": 2147483648,
         "ptuuid": "0x14fc63d2",
         "pttype": "mbr",
         "partitions": [