Repository: osbuild/osbuild-composer
Pull request #157 (2019-12-11 15:20:11)

arches: add more multi-arch support
This is depnedent on @gicmo's osbuild work, and is just meant to showcase how it could be hooked up.


Patch:
diff --git a/cmd/osbuild-pipeline/main.go b/cmd/osbuild-pipeline/main.go
index 8a0162e6c..9fb4e3013 100644
--- a/cmd/osbuild-pipeline/main.go
+++ b/cmd/osbuild-pipeline/main.go
@@ -52,7 +52,7 @@ func main() {
 	}
 
 	rpmmd := rpmmd.NewRPMMD()
-	_, checksums, err := rpmmd.Depsolve(packages, d.Repositories())
+	_, checksums, err := rpmmd.Depsolve(packages, d.Repositories(archArg))
 	if err != nil {
 		panic(err.Error())
 	}
diff --git a/internal/distro/distro.go b/internal/distro/distro.go
index 00a3ecab2..acc24c4e1 100644
--- a/internal/distro/distro.go
+++ b/internal/distro/distro.go
@@ -18,7 +18,7 @@ import (
 type Distro interface {
 	// Returns a list of repositories from which this distribution gets its
 	// content.
-	Repositories() []rpmmd.RepoConfig
+	Repositories(arch string) []rpmmd.RepoConfig
 
 	// Returns a sorted list of the output formats this distro supports.
 	ListOutputFormats() []string
diff --git a/internal/distro/fedora30/distro.go b/internal/distro/fedora30/distro.go
index 8031ca7bb..8d7f4c1f6 100644
--- a/internal/distro/fedora30/distro.go
+++ b/internal/distro/fedora30/distro.go
@@ -14,9 +14,17 @@ import (
 )
 
 type Fedora30 struct {
+	arches  map[string]arch
 	outputs map[string]output
 }
 
+type arch struct {
+	Name               string
+	BootloaderPackages []string
+	BuildPackages      []string
+	UEFI               bool
+}
+
 type output struct {
 	Name             string
 	MimeType         string
@@ -25,15 +33,38 @@ type output struct {
 	EnabledServices  []string
 	DisabledServices []string
 	KernelOptions    string
-	IncludeFSTab     bool
-	Assembler        *pipeline.Assembler
+	Bootable         bool
+	Assembler        func(uefi bool) *pipeline.Assembler
 }
 
 func New() *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",
+		},
+	}
+
+	r.arches["aarch64"] = arch{
+		Name: "aarch64",
+		BootloaderPackages: []string{
+			"dracut-config-generic",
+			"efibootmgr",
+			"grub2-efi-aa64",
+			"grub2-tools",
+			"shim-aa64",
+		},
+		UEFI: true,
+	}
+
 	r.outputs["ami"] = output{
 		Name:     "image.raw.xz",
 		MimeType: "application/octet-stream",
@@ -42,7 +73,6 @@ func New() *Fedora30 {
 			"chrony",
 			"kernel",
 			"selinux-policy-targeted",
-			"grub2-pc",
 			"langpacks-en",
 			"libxcrypt-compat",
 			"xfsprogs",
@@ -57,8 +87,8 @@ func New() *Fedora30 {
 			"cloud-init.service",
 		},
 		KernelOptions: "ro no_timer_check console=ttyS0,115200n8 console=tty1 biosdevname=0 net.ifnames=0 console=ttyS0,115200",
-		IncludeFSTab:  true,
-		Assembler:     r.qemuAssembler("raw.xz", "image.raw.xz"),
+		Bootable:      true,
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("raw.xz", "image.raw.xz", uefi) },
 	}
 
 	r.outputs["ext4-filesystem"] = output{
@@ -76,8 +106,8 @@ func New() *Fedora30 {
 			"dracut-config-rescue",
 		},
 		KernelOptions: "ro biosdevname=0 net.ifnames=0",
-		IncludeFSTab:  false,
-		Assembler:     r.rawFSAssembler("filesystem.img"),
+		Bootable:      false,
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.rawFSAssembler("filesystem.img") },
 	}
 
 	r.outputs["partitioned-disk"] = output{
@@ -87,7 +117,6 @@ func New() *Fedora30 {
 			"@core",
 			"chrony",
 			"firewalld",
-			"grub2-pc",
 			"kernel",
 			"langpacks-en",
 			"selinux-policy-targeted",
@@ -96,8 +125,8 @@ func New() *Fedora30 {
 			"dracut-config-rescue",
 		},
 		KernelOptions: "ro biosdevname=0 net.ifnames=0",
-		IncludeFSTab:  true,
-		Assembler:     r.qemuAssembler("raw", "disk.img"),
+		Bootable:      true,
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("raw", "disk.img", uefi) },
 	}
 
 	r.outputs["qcow2"] = output{
@@ -110,7 +139,6 @@ func New() *Fedora30 {
 			"polkit",
 			"systemd-udev",
 			"selinux-policy-targeted",
-			"grub2-pc",
 			"langpacks-en",
 		},
 		ExcludedPackages: []string{
@@ -121,8 +149,8 @@ func New() *Fedora30 {
 			"plymouth",
 		},
 		KernelOptions: "ro biosdevname=0 net.ifnames=0",
-		IncludeFSTab:  true,
-		Assembler:     r.qemuAssembler("qcow2", "disk.qcow2"),
+		Bootable:      true,
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("qcow2", "disk.qcow2", uefi) },
 	}
 
 	r.outputs["openstack"] = output{
@@ -133,7 +161,6 @@ func New() *Fedora30 {
 			"chrony",
 			"kernel",
 			"selinux-policy-targeted",
-			"grub2-pc",
 			"spice-vdagent",
 			"qemu-guest-agent",
 			"xen-libs",
@@ -145,8 +172,8 @@ func New() *Fedora30 {
 			"dracut-config-rescue",
 		},
 		KernelOptions: "ro biosdevname=0 net.ifnames=0",
-		IncludeFSTab:  true,
-		Assembler:     r.qemuAssembler("qcow2", "image.qcow2"),
+		Bootable:      true,
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("qcow2", "image.qcow2", uefi) },
 	}
 
 	r.outputs["tar"] = output{
@@ -164,8 +191,8 @@ func New() *Fedora30 {
 			"dracut-config-rescue",
 		},
 		KernelOptions: "ro biosdevname=0 net.ifnames=0",
-		IncludeFSTab:  false,
-		Assembler:     r.tarAssembler("root.tar.xz", "xz"),
+		Bootable:      false,
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.tarAssembler("root.tar.xz", "xz") },
 	}
 
 	r.outputs["vhd"] = output{
@@ -176,7 +203,6 @@ func New() *Fedora30 {
 			"chrony",
 			"kernel",
 			"selinux-policy-targeted",
-			"grub2-pc",
 			"langpacks-en",
 			"net-tools",
 			"ntfsprogs",
@@ -187,8 +213,8 @@ func New() *Fedora30 {
 			"dracut-config-rescue",
 		},
 		KernelOptions: "ro biosdevname=0 net.ifnames=0",
-		IncludeFSTab:  true,
-		Assembler:     r.qemuAssembler("vpc", "image.vhd"),
+		Bootable:      true,
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("vpc", "image.vhd", uefi) },
 	}
 
 	r.outputs["vmdk"] = output{
@@ -198,7 +224,6 @@ func New() *Fedora30 {
 			"@core",
 			"chrony",
 			"firewalld",
-			"grub2-pc",
 			"kernel",
 			"langpacks-en",
 			"open-vm-tools",
@@ -208,19 +233,19 @@ func New() *Fedora30 {
 			"dracut-config-rescue",
 		},
 		KernelOptions: "ro biosdevname=0 net.ifnames=0",
-		IncludeFSTab:  true,
-		Assembler:     r.qemuAssembler("vmdk", "disk.vmdk"),
+		Bootable:      true,
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("vmdk", "disk.vmdk", uefi) },
 	}
 
 	return &r
 }
 
-func (r *Fedora30) Repositories() []rpmmd.RepoConfig {
+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=x86_64",
+			Metalink: "https://mirrors.fedoraproject.org/metalink?repo=fedora-30&arch=" + arch,
 			GPGKey: `-----BEGIN PGP PUBLIC KEY BLOCK-----
 
 mQINBFturGcBEACv0xBo91V2n0uEC2vh69ywCiSyvUgN/AQH8EZpCVtM7NyjKgKm
@@ -276,15 +301,19 @@ func (r *Fedora30) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.Repo
 		return nil, errors.New("invalid output format: " + outputFormat)
 	}
 
-	if outputArchitecture != "x86_64" {
-		return nil, errors.New("invalid output architecture: " + outputArchitecture)
+	arch, exists := r.arches[outputArchitecture]
+	if !exists {
+		return nil, errors.New("invalid architecture: " + outputArchitecture)
 	}
 
 	p := &pipeline.Pipeline{}
-	p.SetBuild(r.buildPipeline(checksums), "org.osbuild.fedora30")
+	p.SetBuild(r.buildPipeline(arch, checksums), "org.osbuild.fedora30")
 
 	packages := append(output.Packages, b.GetPackages()...)
-	p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(additionalRepos, checksums, packages, output.ExcludedPackages)))
+	if output.Bootable {
+		packages = append(packages, arch.BootloaderPackages...)
+	}
+	p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(arch, additionalRepos, checksums, packages, output.ExcludedPackages)))
 	p.AddStage(pipeline.NewFixBLSStage())
 
 	// TODO support setting all languages and install corresponding langpack-* package
@@ -327,10 +356,10 @@ func (r *Fedora30) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.Repo
 		p.AddStage(pipeline.NewGroupsStage(r.groupStageOptions(groups)))
 	}
 
-	if output.IncludeFSTab {
-		p.AddStage(pipeline.NewFSTabStage(r.fsTabStageOptions()))
+	if output.Bootable {
+		p.AddStage(pipeline.NewFSTabStage(r.fsTabStageOptions(arch.UEFI)))
 	}
-	p.AddStage(pipeline.NewGRUB2Stage(r.grub2StageOptions(output.KernelOptions, b.GetKernel())))
+	p.AddStage(pipeline.NewGRUB2Stage(r.grub2StageOptions(output.KernelOptions, b.GetKernel(), arch.UEFI)))
 
 	if services := b.GetServices(); services != nil || output.EnabledServices != nil {
 		p.AddStage(pipeline.NewSystemdStage(r.systemdStageOptions(output.EnabledServices, output.DisabledServices, services)))
@@ -341,7 +370,7 @@ func (r *Fedora30) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.Repo
 	}
 
 	p.AddStage(pipeline.NewSELinuxStage(r.selinuxStageOptions()))
-	p.Assembler = output.Assembler
+	p.Assembler = output.Assembler(arch.UEFI)
 
 	return p, nil
 }
@@ -350,28 +379,30 @@ func (r *Fedora30) Runner() string {
 	return "org.osbuild.fedora30"
 }
 
-func (r *Fedora30) buildPipeline(checksums map[string]string) *pipeline.Pipeline {
+func (r *Fedora30) buildPipeline(arch arch, checksums map[string]string) *pipeline.Pipeline {
 	packages := []string{
 		"dnf",
+		"dosfstools",
 		"e2fsprogs",
 		"policycoreutils",
 		"qemu-img",
 		"systemd",
-		"grub2-pc",
 		"tar",
 	}
+	packages = append(packages, arch.BuildPackages...)
 	p := &pipeline.Pipeline{}
-	p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(nil, checksums, packages, nil)))
+	p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(arch, nil, checksums, packages, nil)))
 	return p
 }
 
-func (r *Fedora30) dnfStageOptions(additionalRepos []rpmmd.RepoConfig, checksums map[string]string, packages, excludedPackages []string) *pipeline.DNFStageOptions {
+func (r *Fedora30) dnfStageOptions(arch arch, additionalRepos []rpmmd.RepoConfig, checksums map[string]string, packages, excludedPackages []string) *pipeline.DNFStageOptions {
 	options := &pipeline.DNFStageOptions{
 		ReleaseVersion:   "30",
-		BaseArchitecture: "x86_64",
+		BaseArchitecture: arch.Name,
+		ModulePlatformId: "platform:f30",
 	}
 
-	for _, repo := range append(r.Repositories(), additionalRepos...) {
+	for _, repo := range append(r.Repositories(arch.Name), additionalRepos...) {
 		options.AddRepository(&pipeline.DNFRepository{
 			BaseURL:    repo.BaseURL,
 			MetaLink:   repo.Metalink,
@@ -381,10 +412,12 @@ func (r *Fedora30) dnfStageOptions(additionalRepos []rpmmd.RepoConfig, checksums
 		})
 	}
 
+	sort.Strings(packages)
 	for _, pkg := range packages {
 		options.AddPackage(pkg)
 	}
 
+	sort.Strings(excludedPackages)
 	for _, pkg := range excludedPackages {
 		options.ExcludePackage(pkg)
 	}
@@ -476,17 +509,16 @@ func (r *Fedora30) systemdStageOptions(enabledServices, disabledServices []strin
 	}
 }
 
-func (r *Fedora30) fsTabStageOptions() *pipeline.FSTabStageOptions {
-	id, err := uuid.Parse("76a22bf4-f153-4541-b6c7-0332c0dfaeac")
-	if err != nil {
-		panic("invalid UUID")
-	}
+func (r *Fedora30) fsTabStageOptions(uefi bool) *pipeline.FSTabStageOptions {
 	options := pipeline.FSTabStageOptions{}
-	options.AddFilesystem(id, "ext4", "/", "defaults", 1, 1)
+	options.AddFilesystem("76a22bf4-f153-4541-b6c7-0332c0dfaeac", "ext4", "/", "defaults", 1, 1)
+	if uefi {
+		options.AddFilesystem("46BB-8120", "vfat", "/boot/efi", "umask=0077,shortname=winnt", 0, 2)
+	}
 	return &options
 }
 
-func (r *Fedora30) grub2StageOptions(kernelOptions string, kernel *blueprint.KernelCustomization) *pipeline.GRUB2StageOptions {
+func (r *Fedora30) grub2StageOptions(kernelOptions string, kernel *blueprint.KernelCustomization, uefi bool) *pipeline.GRUB2StageOptions {
 	id, err := uuid.Parse("76a22bf4-f153-4541-b6c7-0332c0dfaeac")
 	if err != nil {
 		panic("invalid UUID")
@@ -496,9 +528,18 @@ func (r *Fedora30) grub2StageOptions(kernelOptions string, kernel *blueprint.Ker
 		kernelOptions += " " + kernel.Append
 	}
 
+	var uefiOptions *pipeline.GRUB2UEFI
+	if uefi {
+		uefiOptions = &pipeline.GRUB2UEFI{
+			Vendor: "fedora",
+		}
+	}
+
 	return &pipeline.GRUB2StageOptions{
 		RootFilesystemUUID: id,
 		KernelOptions:      kernelOptions,
+		Legacy:             !uefi,
+		UEFI:               uefiOptions,
 	}
 }
 
@@ -508,25 +549,65 @@ func (r *Fedora30) selinuxStageOptions() *pipeline.SELinuxStageOptions {
 	}
 }
 
-func (r *Fedora30) qemuAssembler(format string, filename string) *pipeline.Assembler {
-	id, err := uuid.Parse("76a22bf4-f153-4541-b6c7-0332c0dfaeac")
-	if err != nil {
-		panic("invalid UUID")
+func (r *Fedora30) qemuAssembler(format string, filename string, uefi bool) *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,
+			PTUUID:   "8DFDFF87-C96E-EA48-A3A6-9408F1F6B1EF",
+			PTType:   "gpt",
+			Partitions: []pipeline.QEMUPartition{
+				{
+					Start: 2048,
+					Size:  972800,
+					Type:  &fstype,
+					Filesystem: pipeline.QEMUFilesystem{
+						Type:       "vfat",
+						UUID:       "46BB-8120",
+						Label:      "EFI System Partition",
+						Mountpoint: "/boot/efi",
+					},
+				},
+				{
+					Start: 976896,
+					Filesystem: pipeline.QEMUFilesystem{
+						Type:       "ext4",
+						UUID:       "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
+						Mountpoint: "/",
+					},
+				},
+			},
+		}
+	} else {
+		options = pipeline.QEMUAssemblerOptions{
+			Format:   format,
+			Filename: filename,
+			Size:     3222274048,
+			PTUUID:   "0x14fc63d2",
+			PTType:   "mbr",
+			Partitions: []pipeline.QEMUPartition{
+				{
+					Start:    2048,
+					Bootable: true,
+					Filesystem: pipeline.QEMUFilesystem{
+						Type:       "ext4",
+						UUID:       "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
+						Mountpoint: "/",
+					},
+				},
+			},
+		}
 	}
-	return pipeline.NewQEMUAssembler(
-		&pipeline.QEMUAssemblerOptions{
-			Format:             format,
-			Filename:           filename,
-			PTUUID:             "0x14fc63d2",
-			RootFilesystemUUDI: id,
-			Size:               3222274048,
-		})
+	return pipeline.NewQEMUAssembler(&options)
 }
 
 func (r *Fedora30) tarAssembler(filename, compression string) *pipeline.Assembler {
 	return pipeline.NewTarAssembler(
 		&pipeline.TarAssemblerOptions{
-			Filename: filename,
+			Filename:    filename,
 			Compression: compression,
 		})
 }
diff --git a/internal/distro/rhel82/distro.go b/internal/distro/rhel82/distro.go
index 629d66912..11d05336e 100644
--- a/internal/distro/rhel82/distro.go
+++ b/internal/distro/rhel82/distro.go
@@ -14,9 +14,17 @@ import (
 )
 
 type RHEL82 struct {
+	arches  map[string]arch
 	outputs map[string]output
 }
 
+type arch struct {
+	Name               string
+	BootloaderPackages []string
+	BuildPackages      []string
+	UEFI               bool
+}
+
 type output struct {
 	Name             string
 	MimeType         string
@@ -24,19 +32,42 @@ type output struct {
 	ExcludedPackages []string
 	EnabledServices  []string
 	DisabledServices []string
-	IncludeFSTab     bool
+	Bootable         bool
 	DefaultTarget    string
 	KernelOptions    string
-	Assembler        *pipeline.Assembler
+	Assembler        func(uefi bool) *pipeline.Assembler
 }
 
 func New() *RHEL82 {
 	const GigaByte = 1024 * 1024 * 1024
 
 	r := RHEL82{
+		arches:  map[string]arch{},
 		outputs: map[string]output{},
 	}
 
+	r.arches["x86_64"] = arch{
+		Name: "x86_64",
+		BootloaderPackages: []string{
+			"grub2-pc",
+		},
+		BuildPackages: []string{
+			"grub2-pc",
+		},
+	}
+
+	r.arches["aarch64"] = arch{
+		Name: "aarch64",
+		BootloaderPackages: []string{
+			"dracut-config-generic",
+			"efibootmgr",
+			"grub2-efi-aa64",
+			"grub2-tools",
+			"shim-aa64",
+		},
+		UEFI: true,
+	}
+
 	r.outputs["ami"] = output{
 		Name:     "image.raw.xz",
 		MimeType: "application/octet-stream",
@@ -50,7 +81,6 @@ func New() *RHEL82 {
 			"dhcp-client",
 			"dracut-config-generic",
 			"gdisk",
-			"grub2",
 			"insights-client",
 			"kernel",
 			"langpacks-en",
@@ -107,9 +137,11 @@ func New() *RHEL82 {
 			"timedatex",
 		},
 		DefaultTarget: "multi-user.target",
-		IncludeFSTab:  true,
+		Bootable:      true,
 		KernelOptions: "ro console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto",
-		Assembler:     r.qemuAssembler("raw.xz", "image.raw.xz", 6*GigaByte),
+		Assembler: func(uefi bool) *pipeline.Assembler {
+			return r.qemuAssembler("raw.xz", "image.raw.xz", 6*GigaByte, uefi)
+		},
 	}
 
 	r.outputs["ext4-filesystem"] = output{
@@ -131,9 +163,9 @@ func New() *RHEL82 {
 			// https://errata.devel.redhat.com/advisory/47339 lands
 			"timedatex",
 		},
-		IncludeFSTab:  false,
+		Bootable:      false,
 		KernelOptions: "ro net.ifnames=0",
-		Assembler:     r.rawFSAssembler("filesystem.img"),
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.rawFSAssembler("filesystem.img") },
 	}
 
 	r.outputs["partitioned-disk"] = output{
@@ -144,7 +176,6 @@ func New() *RHEL82 {
 			"chrony",
 			"dracut-config-generic",
 			"firewalld",
-			"grub2-pc",
 			"kernel",
 			"langpacks-en",
 			"selinux-policy-targeted",
@@ -156,9 +187,9 @@ func New() *RHEL82 {
 			// https://errata.devel.redhat.com/advisory/47339 lands
 			"timedatex",
 		},
-		IncludeFSTab:  true,
+		Bootable:      true,
 		KernelOptions: "ro net.ifnames=0",
-		Assembler:     r.qemuAssembler("raw", "disk.img", 3*GigaByte),
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("raw", "disk.img", 3*GigaByte, uefi) },
 	}
 
 	r.outputs["qcow2"] = output{
@@ -171,7 +202,6 @@ func New() *RHEL82 {
 			"polkit",
 			"systemd-udev",
 			"selinux-policy-targeted",
-			"grub2-pc",
 			"langpacks-en",
 		},
 		ExcludedPackages: []string{
@@ -185,9 +215,9 @@ func New() *RHEL82 {
 			// https://errata.devel.redhat.com/advisory/47339 lands
 			"timedatex",
 		},
-		IncludeFSTab:  true,
+		Bootable:      true,
 		KernelOptions: "ro net.ifnames=0",
-		Assembler:     r.qemuAssembler("qcow2", "disk.qcow2", 3*GigaByte),
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("qcow2", "disk.qcow2", 3*GigaByte, uefi) },
 	}
 
 	r.outputs["openstack"] = output{
@@ -196,7 +226,6 @@ func New() *RHEL82 {
 		Packages: []string{
 			// Defaults
 			"@Core",
-			"grub2-pc",
 			"langpacks-en",
 
 			// Don't run dracut in host-only mode, in order to pull in
@@ -213,9 +242,9 @@ func New() *RHEL82 {
 		ExcludedPackages: []string{
 			"dracut-config-rescue",
 		},
-		IncludeFSTab:  true,
+		Bootable:      true,
 		KernelOptions: "ro net.ifnames=0",
-		Assembler:     r.qemuAssembler("qcow2", "image.qcow2", 3*GigaByte),
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("qcow2", "image.qcow2", 3*GigaByte, uefi) },
 	}
 
 	r.outputs["tar"] = output{
@@ -237,9 +266,9 @@ func New() *RHEL82 {
 			// https://errata.devel.redhat.com/advisory/47339 lands
 			"timedatex",
 		},
-		IncludeFSTab:  false,
+		Bootable:      false,
 		KernelOptions: "ro net.ifnames=0",
-		Assembler:     r.tarAssembler("root.tar.xz", "xz"),
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.tarAssembler("root.tar.xz", "xz") },
 	}
 
 	r.outputs["vhd"] = output{
@@ -248,7 +277,6 @@ func New() *RHEL82 {
 		Packages: []string{
 			// Defaults
 			"@Core",
-			"grub2-pc",
 			"langpacks-en",
 
 			// Don't run dracut in host-only mode, in order to pull in
@@ -278,9 +306,9 @@ func New() *RHEL82 {
 			"waagent",
 		},
 		DefaultTarget: "multi-user.target",
-		IncludeFSTab:  true,
+		Bootable:      true,
 		KernelOptions: "ro biosdevname=0 rootdelay=300 console=ttyS0 earlyprintk=ttyS0 net.ifnames=0",
-		Assembler:     r.qemuAssembler("vpc", "image.vhd", 3*GigaByte),
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("vpc", "image.vhd", 3*GigaByte, uefi) },
 	}
 
 	r.outputs["vmdk"] = output{
@@ -291,7 +319,6 @@ func New() *RHEL82 {
 			"chrony",
 			"dracut-config-generic",
 			"firewalld",
-			"grub2-pc",
 			"kernel",
 			"langpacks-en",
 			"open-vm-tools",
@@ -304,25 +331,25 @@ func New() *RHEL82 {
 			// https://errata.devel.redhat.com/advisory/47339 lands
 			"timedatex",
 		},
-		IncludeFSTab:  true,
+		Bootable:      true,
 		KernelOptions: "ro net.ifnames=0",
-		Assembler:     r.qemuAssembler("vmdk", "disk.vmdk", 3*GigaByte),
+		Assembler:     func(uefi bool) *pipeline.Assembler { return r.qemuAssembler("vmdk", "disk.vmdk", 3*GigaByte, uefi) },
 	}
 
 	return &r
 }
 
-func (r *RHEL82) Repositories() []rpmmd.RepoConfig {
+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/x86_64/os",
+			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/x86_64/os",
+			BaseURL: "http://download-ipv4.eng.brq.redhat.com/rhel-8/nightly/RHEL-8/RHEL-8.2.0-20191213.n.1/compose/AppStream/" + arch + "/os",
 		},
 	}
 }
@@ -349,26 +376,30 @@ func (r *RHEL82) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.RepoCo
 		return nil, errors.New("invalid output format: " + outputFormat)
 	}
 
-	if outputArchitecture != "x86_64" {
-		return nil, errors.New("invalid output architecture: " + outputArchitecture)
+	arch, exists := r.arches[outputArchitecture]
+	if !exists {
+		return nil, errors.New("invalid architecture: " + outputArchitecture)
 	}
 
 	p := &pipeline.Pipeline{}
-	p.SetBuild(r.buildPipeline(checksums), "org.osbuild.rhel82")
+	p.SetBuild(r.buildPipeline(arch, checksums), "org.osbuild.rhel82")
 
 	packages := append(output.Packages, b.GetPackages()...)
-	p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(additionalRepos, checksums, packages, output.ExcludedPackages)))
+	if output.Bootable {
+		packages = append(packages, arch.BootloaderPackages...)
+	}
+	p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(arch, additionalRepos, checksums, packages, output.ExcludedPackages)))
 	p.AddStage(pipeline.NewFixBLSStage())
 
-	if output.IncludeFSTab {
-		p.AddStage(pipeline.NewFSTabStage(r.fsTabStageOptions()))
+	if output.Bootable {
+		p.AddStage(pipeline.NewFSTabStage(r.fsTabStageOptions(arch.UEFI)))
 	}
 
 	kernelOptions := output.KernelOptions
 	if kernel := b.GetKernel(); kernel != nil {
 		kernelOptions += " " + kernel.Append
 	}
-	p.AddStage(pipeline.NewGRUB2Stage(r.grub2StageOptions(kernelOptions)))
+	p.AddStage(pipeline.NewGRUB2Stage(r.grub2StageOptions(kernelOptions, arch.UEFI)))
 
 	// TODO support setting all languages and install corresponding langpack-* package
 	language, keyboard := b.GetPrimaryLocale()
@@ -419,7 +450,7 @@ func (r *RHEL82) Pipeline(b *blueprint.Blueprint, additionalRepos []rpmmd.RepoCo
 	}
 
 	p.AddStage(pipeline.NewSELinuxStage(r.selinuxStageOptions()))
-	p.Assembler = output.Assembler
+	p.Assembler = output.Assembler(arch.UEFI)
 
 	return p, nil
 }
@@ -428,13 +459,13 @@ func (r *RHEL82) Runner() string {
 	return "org.osbuild.rhel82"
 }
 
-func (r *RHEL82) buildPipeline(checksums map[string]string) *pipeline.Pipeline {
+func (r *RHEL82) buildPipeline(arch arch, checksums map[string]string) *pipeline.Pipeline {
 	packages := []string{
 		"dnf",
+		"dosfstools",
 		"dracut-config-generic",
 		"e2fsprogs",
 		"glibc",
-		"grub2-pc",
 		"policycoreutils",
 		"python36",
 		"qemu-img",
@@ -442,18 +473,19 @@ func (r *RHEL82) buildPipeline(checksums map[string]string) *pipeline.Pipeline {
 		"tar",
 		"xfsprogs",
 	}
+	packages = append(packages, arch.BuildPackages...)
 	p := &pipeline.Pipeline{}
-	p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(nil, checksums, packages, nil)))
+	p.AddStage(pipeline.NewDNFStage(r.dnfStageOptions(arch, nil, checksums, packages, nil)))
 	return p
 }
 
-func (r *RHEL82) dnfStageOptions(additionalRepos []rpmmd.RepoConfig, checksums map[string]string, packages, excludedPackages []string) *pipeline.DNFStageOptions {
+func (r *RHEL82) dnfStageOptions(arch arch, additionalRepos []rpmmd.RepoConfig, checksums map[string]string, packages, excludedPackages []string) *pipeline.DNFStageOptions {
 	options := &pipeline.DNFStageOptions{
 		ReleaseVersion:   "8",
-		BaseArchitecture: "x86_64",
+		BaseArchitecture: arch.Name,
 		ModulePlatformId: "platform:el8",
 	}
-	for _, repo := range append(r.Repositories(), additionalRepos...) {
+	for _, repo := range append(r.Repositories(arch.Name), additionalRepos...) {
 		options.AddRepository(&pipeline.DNFRepository{
 			BaseURL:    repo.BaseURL,
 			MetaLink:   repo.Metalink,
@@ -462,10 +494,12 @@ func (r *RHEL82) dnfStageOptions(additionalRepos []rpmmd.RepoConfig, checksums m
 		})
 	}
 
+	sort.Strings(packages)
 	for _, pkg := range packages {
 		options.AddPackage(pkg)
 	}
 
+	sort.Strings(excludedPackages)
 	for _, pkg := range excludedPackages {
 		options.ExcludePackage(pkg)
 	}
@@ -558,24 +592,33 @@ func (r *RHEL82) systemdStageOptions(enabledServices, disabledServices []string,
 	}
 }
 
-func (r *RHEL82) fsTabStageOptions() *pipeline.FSTabStageOptions {
-	id, err := uuid.Parse("0bd700f8-090f-4556-b797-b340297ea1bd")
-	if err != nil {
-		panic("invalid UUID")
-	}
+func (r *RHEL82) fsTabStageOptions(uefi bool) *pipeline.FSTabStageOptions {
 	options := pipeline.FSTabStageOptions{}
-	options.AddFilesystem(id, "xfs", "/", "defaults", 0, 0)
+	options.AddFilesystem("0bd700f8-090f-4556-b797-b340297ea1bd", "xfs", "/", "defaults", 0, 0)
+	if uefi {
+		options.AddFilesystem("46BB-8120", "vfat", "/boot/efi", "umask=0077,shortname=winnt", 0, 2)
+	}
 	return &options
 }
 
-func (r *RHEL82) grub2StageOptions(kernelOptions string) *pipeline.GRUB2StageOptions {
+func (r *RHEL82) grub2StageOptions(kernelOptions string, uefi bool) *pipeline.GRUB2StageOptions {
 	id, err := uuid.Parse("0bd700f8-090f-4556-b797-b340297ea1bd")
 	if err != nil {
 		panic("invalid UUID")
 	}
+
+	var uefiOptions *pipeline.GRUB2UEFI
+	if uefi {
+		uefiOptions = &pipeline.GRUB2UEFI{
+			Vendor: "redhat",
+		}
+	}
+
 	return &pipeline.GRUB2StageOptions{
 		RootFilesystemUUID: id,
 		KernelOptions:      kernelOptions,
+		Legacy:             !uefi,
+		UEFI:               uefiOptions,
 	}
 }
 
@@ -585,20 +628,59 @@ func (r *RHEL82) selinuxStageOptions() *pipeline.SELinuxStageOptions {
 	}
 }
 
-func (r *RHEL82) qemuAssembler(format string, filename string, size uint64) *pipeline.Assembler {
-	id, err := uuid.Parse("0bd700f8-090f-4556-b797-b340297ea1bd")
-	if err != nil {
-		panic("invalid UUID")
+func (r *RHEL82) qemuAssembler(format string, filename string, size uint64, uefi bool) *pipeline.Assembler {
+	var options pipeline.QEMUAssemblerOptions
+	if uefi {
+		fstype := uuid.MustParse("C12A7328-F81F-11D2-BA4B-00A0C93EC93B")
+		options = pipeline.QEMUAssemblerOptions{
+			Format:   format,
+			Filename: filename,
+			Size:     size,
+			PTUUID:   "8DFDFF87-C96E-EA48-A3A6-9408F1F6B1EF",
+			PTType:   "gpt",
+			Partitions: []pipeline.QEMUPartition{
+				{
+					Start: 2048,
+					Size:  972800,
+					Type:  &fstype,
+					Filesystem: pipeline.QEMUFilesystem{
+						Type:       "vfat",
+						UUID:       "46BB-8120",
+						Label:      "EFI System Partition",
+						Mountpoint: "/boot/efi",
+					},
+				},
+				{
+					Start: 976896,
+					Filesystem: pipeline.QEMUFilesystem{
+						Type:       "xfs",
+						UUID:       "0bd700f8-090f-4556-b797-b340297ea1bd",
+						Mountpoint: "/",
+					},
+				},
+			},
+		}
+	} else {
+		options = pipeline.QEMUAssemblerOptions{
+			Format:   format,
+			Filename: filename,
+			Size:     size,
+			PTUUID:   "0x14fc63d2",
+			PTType:   "mbr",
+			Partitions: []pipeline.QEMUPartition{
+				{
+					Start:    2048,
+					Bootable: true,
+					Filesystem: pipeline.QEMUFilesystem{
+						Type:       "xfs",
+						UUID:       "0bd700f8-090f-4556-b797-b340297ea1bd",
+						Mountpoint: "/",
+					},
+				},
+			},
+		}
 	}
-	return pipeline.NewQEMUAssembler(
-		&pipeline.QEMUAssemblerOptions{
-			Format:             format,
-			Filename:           filename,
-			PTUUID:             "0x14fc63d2",
-			RootFilesystemUUDI: id,
-			Size:               size,
-			RootFilesystemType: "xfs",
-		})
+	return pipeline.NewQEMUAssembler(&options)
 }
 
 func (r *RHEL82) tarAssembler(filename, compression string) *pipeline.Assembler {
diff --git a/internal/pipeline/fstab_stage.go b/internal/pipeline/fstab_stage.go
index 3a642d4f4..70abc468f 100644
--- a/internal/pipeline/fstab_stage.go
+++ b/internal/pipeline/fstab_stage.go
@@ -1,7 +1,5 @@
 package pipeline
 
-import "github.com/google/uuid"
-
 // The FSTabStageOptions describe the content of the /etc/fstab file.
 //
 // The structure of the options follows the format of /etc/fstab, except
@@ -24,16 +22,16 @@ func NewFSTabStage(options *FSTabStageOptions) *Stage {
 // An FSTabEntry represents one line in /etc/fstab. With the one exception
 // that the the spec field must be represented as an UUID.
 type FSTabEntry struct {
-	UUID    uuid.UUID `json:"uuid"`
-	VFSType string    `json:"vfs_type"`
-	Path    string    `json:"path,omitempty"`
-	Options string    `json:"options,omitempty"`
-	Freq    uint64    `json:"freq,omitempty"`
-	PassNo  uint64    `json:"passno,omitempty"`
+	UUID    string `json:"uuid"`
+	VFSType string `json:"vfs_type"`
+	Path    string `json:"path,omitempty"`
+	Options string `json:"options,omitempty"`
+	Freq    uint64 `json:"freq,omitempty"`
+	PassNo  uint64 `json:"passno,omitempty"`
 }
 
 // AddFilesystem adds one entry to and FSTabStageOptions object.
-func (options *FSTabStageOptions) AddFilesystem(id uuid.UUID, vfsType string, path string, opts string, freq uint64, passNo uint64) {
+func (options *FSTabStageOptions) AddFilesystem(id string, vfsType string, path string, opts string, freq uint64, passNo uint64) {
 	options.FileSystems = append(options.FileSystems, &FSTabEntry{
 		UUID:    id,
 		VFSType: vfsType,
diff --git a/internal/pipeline/grub2_stage.go b/internal/pipeline/grub2_stage.go
index 6c573b597..64fd69eee 100644
--- a/internal/pipeline/grub2_stage.go
+++ b/internal/pipeline/grub2_stage.go
@@ -11,22 +11,19 @@ import "github.com/google/uuid"
 // Note that it is the role of an assembler to install any necessary
 // bootloaders that are stored in the image outside of any filesystem.
 type GRUB2StageOptions struct {
-	RootFilesystemUUID uuid.UUID `json:"root_fs_uuid"`
-	BootFilesystemUUID uuid.UUID `json:"boot_fs_uuid,omitempty"`
-	KernelOptions      string    `json:"kernel_opts,omitempty"`
+	RootFilesystemUUID uuid.UUID  `json:"root_fs_uuid"`
+	BootFilesystemUUID *uuid.UUID `json:"boot_fs_uuid,omitempty"`
+	KernelOptions      string     `json:"kernel_opts,omitempty"`
+	Legacy             bool       `json:"legacy"`
+	UEFI               *GRUB2UEFI `json:"uefi,omitempty"`
 }
 
-func (GRUB2StageOptions) isStageOptions() {}
-
-// NewGRUB2StageOptions creates a new GRUB2StageOptions object. It sets the
-// mandatory options.
-func NewGRUB2StageOptions(rootFilesystemUUID uuid.UUID) *GRUB2StageOptions {
-	return &GRUB2StageOptions{
-		RootFilesystemUUID: rootFilesystemUUID,
-	}
-
+type GRUB2UEFI struct {
+	Vendor string `json:"vendor"`
 }
 
+func (GRUB2StageOptions) isStageOptions() {}
+
 // NewGRUB2Stage creates a new GRUB2 stage object.
 func NewGRUB2Stage(options *GRUB2StageOptions) *Stage {
 	return &Stage{
@@ -34,18 +31,3 @@ func NewGRUB2Stage(options *GRUB2StageOptions) *Stage {
 		Options: options,
 	}
 }
-
-// SetRootFilesystemUUID sets the UUID of the filesystem containing /.
-func (options *GRUB2StageOptions) SetRootFilesystemUUID(u uuid.UUID) {
-	options.RootFilesystemUUID = u
-}
-
-// SetBootFilesystemUUID sets the UUID of the filesystem containing /boot.
-func (options *GRUB2StageOptions) SetBootFilesystemUUID(u uuid.UUID) {
-	options.BootFilesystemUUID = u
-}
-
-// SetKernelOptions sets the kernel options that should be passed at boot.
-func (options *GRUB2StageOptions) SetKernelOptions(kernelOptions string) {
-	options.KernelOptions = kernelOptions
-}
diff --git a/internal/pipeline/qemu_assembler.go b/internal/pipeline/qemu_assembler.go
index b399aa40e..d478f31aa 100644
--- a/internal/pipeline/qemu_assembler.go
+++ b/internal/pipeline/qemu_assembler.go
@@ -4,34 +4,36 @@ import "github.com/google/uuid"
 
 // QEMUAssemblerOptions desrcibe how to assemble a tree into an image using qemu.
 //
-// The assembler creates an image of a the given size, adds a GRUB2 bootloader
-// and a DOS partition table to it with the given PTUUID containing one ext4
-// root partition with the given filesystem UUID and installs the filesystem
-// tree into it. Finally, the image is converted into the target format and
-// stored with the given filename.
+// The assembler creates an image of the given size, adds a GRUB2 bootloader
+// and if necessary and a partition table to it with the given PTUUID
+// containing the indicated partitions. Finally, the image is converted into
+// the target format and stored with the given filename.
 type QEMUAssemblerOptions struct {
-	Format             string    `json:"format"`
-	Filename           string    `json:"filename"`
-	PTUUID             string    `json:"ptuuid"`
-	RootFilesystemUUDI uuid.UUID `json:"root_fs_uuid"`
-	RootFilesystemType string    `json:"root_fs_type"`
-	Size               uint64    `json:"size"`
+	Format     string          `json:"format"`
+	Filename   string          `json:"filename"`
+	Size       uint64          `json:"size"`
+	PTUUID     string          `json:"ptuuid"`
+	PTType     string          `json:"pttype"`
+	Partitions []QEMUPartition `json:"partitions"`
 }
 
-func (QEMUAssemblerOptions) isAssemblerOptions() {}
+type QEMUPartition struct {
+	Start      uint64         `json:"start"`
+	Size       uint64         `json:"size,omitempty"`
+	Type       *uuid.UUID     `json:"type,omitempty"`
+	Bootable   bool           `json:"bootable,omitempty"`
+	Filesystem QEMUFilesystem `json:"filesystem"`
+}
 
-// NewQEMUAssemblerOptions creates a now QEMUAssemblerOptions object, with all the mandatory
-// fields set.
-func NewQEMUAssemblerOptions(format string, ptUUID string, filename string, rootFilesystemUUID uuid.UUID, size uint64) *QEMUAssemblerOptions {
-	return &QEMUAssemblerOptions{
-		Format:             format,
-		PTUUID:             ptUUID,
-		Filename:           filename,
-		RootFilesystemUUDI: rootFilesystemUUID,
-		Size:               size,
-	}
+type QEMUFilesystem struct {
+	Type       string `json:"type"`
+	UUID       string `json:"uuid"`
+	Label      string `json:"label,omitempty"`
+	Mountpoint string `json:"mountpoint"`
 }
 
+func (QEMUAssemblerOptions) isAssemblerOptions() {}
+
 // NewQEMUAssembler creates a new QEMU Assembler object.
 func NewQEMUAssembler(options *QEMUAssemblerOptions) *Assembler {
 	return &Assembler{
diff --git a/internal/weldr/api.go b/internal/weldr/api.go
index 1b203a3d0..ca6e75795 100644
--- a/internal/weldr/api.go
+++ b/internal/weldr/api.go
@@ -229,7 +229,7 @@ func (api *API) sourceListHandler(writer http.ResponseWriter, request *http.Requ
 
 	names := api.store.ListSources()
 
-	for _, repo := range api.distro.Repositories() {
+	for _, repo := range api.distro.Repositories(api.arch) {
 		names = append(names, repo.Id)
 	}
 
@@ -271,14 +271,14 @@ func (api *API) sourceInfoHandler(writer http.ResponseWriter, request *http.Requ
 	// if names is "*" we want all sources
 	if names == "*" {
 		sources = api.store.GetAllSources()
-		for _, repo := range api.distro.Repositories() {
+		for _, repo := range api.distro.Repositories(api.arch) {
 			sources[repo.Id] = store.NewSourceConfig(repo, true)
 		}
 	} else {
 		for _, name := range strings.Split(names, ",") {
 			// check if the source is one of the base repos
 			found := false
-			for _, repo := range api.distro.Repositories() {
+			for _, repo := range api.distro.Repositories(api.arch) {
 				if name == repo.Id {
 					sources[repo.Id] = store.NewSourceConfig(repo, true)
 					found = true
@@ -614,7 +614,7 @@ func (api *API) modulesInfoHandler(writer http.ResponseWriter, request *http.Req
 
 	if modulesRequested {
 		for i, _ := range packageInfos {
-			err := packageInfos[i].FillDependencies(api.rpmmd, api.distro.Repositories())
+			err := packageInfos[i].FillDependencies(api.rpmmd, api.distro.Repositories(api.arch))
 			if err != nil {
 				errors := responseError{
 					ID:  errorId,
@@ -644,7 +644,7 @@ func (api *API) projectsDepsolveHandler(writer http.ResponseWriter, request *htt
 
 	names := strings.Split(params.ByName("projects"), ",")
 
-	packages, _, err := api.rpmmd.Depsolve(names, api.distro.Repositories())
+	packages, _, err := api.rpmmd.Depsolve(names, api.distro.Repositories(api.arch))
 
 	if err != nil {
 		errors := responseError{
@@ -1641,7 +1641,7 @@ func (api *API) composeFailedHandler(writer http.ResponseWriter, request *http.R
 
 func (api *API) fetchPackageList() (rpmmd.PackageList, error) {
 	var repos []rpmmd.RepoConfig
-	for _, repo := range api.distro.Repositories() {
+	for _, repo := range api.distro.Repositories(api.arch) {
 		repos = append(repos, repo)
 	}
 	for _, source := range api.store.GetAllSources() {
@@ -1666,7 +1666,7 @@ func (api *API) depsolveBlueprint(bp *blueprint.Blueprint) ([]rpmmd.PackageSpec,
 	}
 
 	var repos []rpmmd.RepoConfig
-	for _, repo := range api.distro.Repositories() {
+	for _, repo := range api.distro.Repositories(api.arch) {
 		repos = append(repos, repo)
 	}
 	for _, source := range api.store.GetAllSources() {


Test patch:
diff --git a/internal/distro/test/distro.go b/internal/distro/test/distro.go
index 7f5c1d216..30f72447d 100644
--- a/internal/distro/test/distro.go
+++ b/internal/distro/test/distro.go
@@ -15,7 +15,7 @@ func init() {
 	distro.Register("test", &TestDistro{})
 }
 
-func (d *TestDistro) Repositories() []rpmmd.RepoConfig {
+func (d *TestDistro) Repositories(arch string) []rpmmd.RepoConfig {
 	return []rpmmd.RepoConfig{
 		{
 			Id:      "test",
diff --git a/internal/jobqueue/api_test.go b/internal/jobqueue/api_test.go
index d93f70dfb..c834f974f 100644
--- a/internal/jobqueue/api_test.go
+++ b/internal/jobqueue/api_test.go
@@ -49,7 +49,7 @@ func TestCreate(t *testing.T) {
 	}
 
 	test.TestRoute(t, api, false, "POST", "/job-queue/v1/jobs", `{}`, http.StatusCreated,
-	`{"id":"ffffffff-ffff-ffff-ffff-ffffffffffff","output_type":"tar","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","e2fsprogs","policycoreutils","qemu-img","systemd","grub2-pc","tar"],"releasever":"30","basearch":"x86_64"}}]},"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":["policycoreutils","selinux-policy-targeted","kernel","firewalld","chrony","langpacks-en"],"exclude_packages":["dracut-config-rescue"],"releasever":"30","basearch":"x86_64"}},{"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","boot_fs_uuid":"00000000-0000-0000-0000-000000000000","kernel_opts":"ro biosdevname=0 net.ifnames=0"}},{"name":"org.osbuild.selinux","options":{"file_contexts":"etc/selinux/targeted/contexts/files/file_contexts"}}],"assembler":{"name":"org.osbuild.tar","options":{"compression":"xz","filename":"root.tar.xz"}}},"targets":[]}`, "created", "uuid")
+		`{"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")
 }
 
 func testUpdateTransition(t *testing.T, from, to string, expectedStatus int) {
diff --git a/internal/pipeline/stage_test.go b/internal/pipeline/stage_test.go
index 3093f69a9..33423dd0b 100644
--- a/internal/pipeline/stage_test.go
+++ b/internal/pipeline/stage_test.go
@@ -5,9 +5,12 @@ import (
 	"encoding/json"
 	"reflect"
 	"testing"
+
+	"github.com/google/uuid"
 )
 
 func TestStage_UnmarshalJSON(t *testing.T) {
+	null_uuid := uuid.MustParse("00000000-0000-0000-0000-000000000000")
 	type fields struct {
 		Name    string
 		Options StageOptions
@@ -112,11 +115,41 @@ func TestStage_UnmarshalJSON(t *testing.T) {
 		{
 			name: "grub2",
 			fields: fields{
-				Name:    "org.osbuild.grub2",
-				Options: &GRUB2StageOptions{},
+				Name: "org.osbuild.grub2",
+				Options: &GRUB2StageOptions{
+					RootFilesystemUUID: null_uuid,
+				},
+			},
+			args: args{
+				data: []byte(`{"name":"org.osbuild.grub2","options":{"root_fs_uuid":"00000000-0000-0000-0000-000000000000","legacy":false}}`),
+			},
+		},
+		{
+			name: "grub2-uefi",
+			fields: fields{
+				Name: "org.osbuild.grub2",
+				Options: &GRUB2StageOptions{
+					RootFilesystemUUID: null_uuid,
+					UEFI: &GRUB2UEFI{
+						Vendor: "vendor",
+					},
+				},
+			},
+			args: args{
+				data: []byte(`{"name":"org.osbuild.grub2","options":{"root_fs_uuid":"00000000-0000-0000-0000-000000000000","legacy":false,"uefi":{"vendor":"vendor"}}}`),
+			},
+		},
+		{
+			name: "grub2-separate-boot",
+			fields: fields{
+				Name: "org.osbuild.grub2",
+				Options: &GRUB2StageOptions{
+					RootFilesystemUUID: null_uuid,
+					BootFilesystemUUID: &null_uuid,
+				},
 			},
 			args: args{
-				data: []byte(`{"name":"org.osbuild.grub2","options":{"root_fs_uuid":"00000000-0000-0000-0000-000000000000","boot_fs_uuid":"00000000-0000-0000-0000-000000000000"}}`),
+				data: []byte(`{"name":"org.osbuild.grub2","options":{"root_fs_uuid":"00000000-0000-0000-0000-000000000000","boot_fs_uuid":"00000000-0000-0000-0000-000000000000","legacy":false}}`),
 			},
 		},
 		{
diff --git a/test/cases/ami_empty_blueprint.json b/test/cases/ami_empty_blueprint.json
index d3fb931d0..789be7101 100644
--- a/test/cases/ami_empty_blueprint.json
+++ b/test/cases/ami_empty_blueprint.json
@@ -28,15 +28,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -56,22 +58,23 @@
           ],
           "packages": [
             "@Core",
+            "checkpolicy",
             "chrony",
-            "kernel",
-            "selinux-policy-targeted",
+            "cloud-init",
             "grub2-pc",
+            "kernel",
             "langpacks-en",
             "libxcrypt-compat",
-            "xfsprogs",
-            "cloud-init",
-            "checkpolicy",
-            "net-tools"
+            "net-tools",
+            "selinux-policy-targeted",
+            "xfsprogs"
           ],
           "exclude_packages": [
             "dracut-config-rescue"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -103,8 +106,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro no_timer_check console=ttyS0,115200n8 console=tty1 biosdevname=0 net.ifnames=0 console=ttyS0,115200"
+          "kernel_opts": "ro no_timer_check console=ttyS0,115200n8 console=tty1 biosdevname=0 net.ifnames=0 console=ttyS0,115200",
+          "legacy": true
         }
       },
       {
@@ -127,9 +130,20 @@
       "options": {
         "format": "raw.xz",
         "filename": "image.raw.xz",
+        "size": 3222274048,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-        "size": 3222274048
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "ext4",
+              "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   },
diff --git a/test/cases/disk_empty_blueprint.json b/test/cases/disk_empty_blueprint.json
index c9b5e719f..77989674b 100644
--- a/test/cases/disk_empty_blueprint.json
+++ b/test/cases/disk_empty_blueprint.json
@@ -25,15 +25,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -64,7 +66,8 @@
             "dracut-config-rescue"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -96,8 +99,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -112,9 +115,20 @@
       "options": {
         "format": "raw",
         "filename": "disk.img",
+        "size": 3222274048,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-        "size": 3222274048
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "ext4",
+              "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   },
diff --git a/test/cases/disk_local_boot.json b/test/cases/disk_local_boot.json
index e23cf1e38..4b1d634cf 100644
--- a/test/cases/disk_local_boot.json
+++ b/test/cases/disk_local_boot.json
@@ -53,15 +53,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -92,7 +94,8 @@
             "dracut-config-rescue"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -120,8 +123,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -165,9 +168,20 @@
       "options": {
         "format": "raw",
         "filename": "disk.img",
+        "size": 3222274048,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-        "size": 3222274048
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "ext4",
+              "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   }
diff --git a/test/cases/ext4_empty_blueprint.json b/test/cases/ext4_empty_blueprint.json
index 9e080db4f..3d67acc6f 100644
--- a/test/cases/ext4_empty_blueprint.json
+++ b/test/cases/ext4_empty_blueprint.json
@@ -25,15 +25,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -52,18 +54,19 @@
             }
           ],
           "packages": [
-            "policycoreutils",
-            "selinux-policy-targeted",
-            "kernel",
-            "firewalld",
             "chrony",
-            "langpacks-en"
+            "firewalld",
+            "kernel",
+            "langpacks-en",
+            "policycoreutils",
+            "selinux-policy-targeted"
           ],
           "exclude_packages": [
             "dracut-config-rescue"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -80,8 +83,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
diff --git a/test/cases/ext4_local_boot.json b/test/cases/ext4_local_boot.json
index 112e2b7f4..2465bf64f 100644
--- a/test/cases/ext4_local_boot.json
+++ b/test/cases/ext4_local_boot.json
@@ -53,15 +53,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -80,19 +82,20 @@
             }
           ],
           "packages": [
-            "policycoreutils",
-            "selinux-policy-targeted",
-            "kernel",
-            "firewalld",
             "chrony",
+            "firewalld",
+            "kernel",
             "langpacks-en",
-            "openssh-server"
+            "openssh-server",
+            "policycoreutils",
+            "selinux-policy-targeted"
           ],
           "exclude_packages": [
             "dracut-config-rescue"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -105,8 +108,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
diff --git a/test/cases/openstack_empty_blueprint.json b/test/cases/openstack_empty_blueprint.json
index cfa33627c..be183a83f 100644
--- a/test/cases/openstack_empty_blueprint.json
+++ b/test/cases/openstack_empty_blueprint.json
@@ -25,15 +25,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -54,21 +56,22 @@
           "packages": [
             "@Core",
             "chrony",
+            "cloud-init",
+            "grub2-pc",
             "kernel",
+            "langpacks-en",
+            "libdrm",
+            "qemu-guest-agent",
             "selinux-policy-targeted",
-            "grub2-pc",
             "spice-vdagent",
-            "qemu-guest-agent",
-            "xen-libs",
-            "langpacks-en",
-            "cloud-init",
-            "libdrm"
+            "xen-libs"
           ],
           "exclude_packages": [
             "dracut-config-rescue"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -100,8 +103,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -116,9 +119,20 @@
       "options": {
         "format": "qcow2",
         "filename": "image.qcow2",
+        "size": 3222274048,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-        "size": 3222274048
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "ext4",
+              "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   },
diff --git a/test/cases/openstack_local_boot.json b/test/cases/openstack_local_boot.json
index f1736dc28..371126db4 100644
--- a/test/cases/openstack_local_boot.json
+++ b/test/cases/openstack_local_boot.json
@@ -53,15 +53,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -82,21 +84,22 @@
           "packages": [
             "@Core",
             "chrony",
+            "cloud-init",
+            "grub2-pc",
             "kernel",
+            "langpacks-en",
+            "libdrm",
+            "qemu-guest-agent",
             "selinux-policy-targeted",
-            "grub2-pc",
             "spice-vdagent",
-            "qemu-guest-agent",
-            "xen-libs",
-            "langpacks-en",
-            "cloud-init",
-            "libdrm"
+            "xen-libs"
           ],
           "exclude_packages": [
             "dracut-config-rescue"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -124,8 +127,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -166,9 +169,20 @@
       "options": {
         "format": "qcow2",
         "filename": "image.qcow2",
+        "size": 3222274048,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-        "size": 3222274048
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "ext4",
+              "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   }
diff --git a/test/cases/qcow2_empty_blueprint.json b/test/cases/qcow2_empty_blueprint.json
index af2f83841..a018fccdf 100644
--- a/test/cases/qcow2_empty_blueprint.json
+++ b/test/cases/qcow2_empty_blueprint.json
@@ -25,15 +25,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -52,14 +54,14 @@
             }
           ],
           "packages": [
-            "kernel-core",
             "@Fedora Cloud Server",
             "chrony",
+            "grub2-pc",
+            "kernel-core",
+            "langpacks-en",
             "polkit",
-            "systemd-udev",
             "selinux-policy-targeted",
-            "grub2-pc",
-            "langpacks-en"
+            "systemd-udev"
           ],
           "exclude_packages": [
             "dracut-config-rescue",
@@ -69,7 +71,8 @@
             "plymouth"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -101,8 +104,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -117,9 +120,20 @@
       "options": {
         "format": "qcow2",
         "filename": "disk.qcow2",
+        "size": 3222274048,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-        "size": 3222274048
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "ext4",
+              "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   },
diff --git a/test/cases/qcow2_local_boot.json b/test/cases/qcow2_local_boot.json
index 6c12c4dae..109fe6cb8 100644
--- a/test/cases/qcow2_local_boot.json
+++ b/test/cases/qcow2_local_boot.json
@@ -53,15 +53,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -80,14 +82,14 @@
             }
           ],
           "packages": [
-            "kernel-core",
             "@Fedora Cloud Server",
             "chrony",
+            "grub2-pc",
+            "kernel-core",
+            "langpacks-en",
             "polkit",
-            "systemd-udev",
             "selinux-policy-targeted",
-            "grub2-pc",
-            "langpacks-en"
+            "systemd-udev"
           ],
           "exclude_packages": [
             "dracut-config-rescue",
@@ -97,7 +99,8 @@
             "plymouth"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -125,8 +128,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -167,9 +170,20 @@
       "options": {
         "format": "qcow2",
         "filename": "disk.qcow2",
+        "size": 3222274048,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-        "size": 3222274048
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "ext4",
+              "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   }
diff --git a/test/cases/rhel82_ami.json b/test/cases/rhel82_ami.json
index 0e56b81e5..b9b146080 100644
--- a/test/cases/rhel82_ami.json
+++ b/test/cases/rhel82_ami.json
@@ -32,6 +32,7 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "dracut-config-generic",
                 "e2fsprogs",
                 "glibc",
@@ -67,21 +68,21 @@
             }
           ],
           "packages": [
+            "@core",
+            "NetworkManager",
             "checkpolicy",
             "chrony",
             "cloud-init",
             "cloud-init",
             "cloud-utils-growpart",
-            "@core",
             "dhcp-client",
             "dracut-config-generic",
             "gdisk",
-            "grub2",
+            "grub2-pc",
             "insights-client",
             "kernel",
             "langpacks-en",
             "net-tools",
-            "NetworkManager",
             "redhat-release",
             "redhat-release-eula",
             "rng-tools",
@@ -100,8 +101,8 @@
             "firewalld",
             "iprutils",
             "ivtv-firmware",
-            "iwl1000-firmware",
             "iwl100-firmware",
+            "iwl1000-firmware",
             "iwl105-firmware",
             "iwl135-firmware",
             "iwl2000-firmware",
@@ -148,8 +149,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto"
+          "kernel_opts": "ro console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto",
+          "legacy": true
         }
       },
       {
@@ -170,10 +171,20 @@
       "options": {
         "format": "raw.xz",
         "filename": "image.raw.xz",
+        "size": 6442450944,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-        "root_fs_type": "xfs",
-        "size": 6442450944
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "xfs",
+              "uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   }
diff --git a/test/cases/rhel82_ext4_filesystem.json b/test/cases/rhel82_ext4_filesystem.json
index 120515f71..8f26c72da 100644
--- a/test/cases/rhel82_ext4_filesystem.json
+++ b/test/cases/rhel82_ext4_filesystem.json
@@ -38,6 +38,7 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "dracut-config-generic",
                 "e2fsprogs",
                 "glibc",
@@ -73,13 +74,13 @@
             }
           ],
           "packages": [
-            "policycoreutils",
-            "selinux-policy-targeted",
-            "kernel",
-            "firewalld",
             "chrony",
             "dracut-config-generic",
-            "langpacks-en"
+            "firewalld",
+            "kernel",
+            "langpacks-en",
+            "policycoreutils",
+            "selinux-policy-targeted"
           ],
           "exclude_packages": [
             "dracut-config-rescue",
@@ -98,8 +99,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro net.ifnames=0"
+          "kernel_opts": "ro net.ifnames=0",
+          "legacy": true
         }
       },
       {
diff --git a/test/cases/rhel82_openstack.json b/test/cases/rhel82_openstack.json
index 9614855af..83e53b7e5 100644
--- a/test/cases/rhel82_openstack.json
+++ b/test/cases/rhel82_openstack.json
@@ -32,6 +32,7 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "dracut-config-generic",
                 "e2fsprogs",
                 "glibc",
@@ -68,13 +69,13 @@
           ],
           "packages": [
             "@Core",
-            "grub2-pc",
-            "langpacks-en",
+            "cloud-init",
             "dracut-config-generic",
+            "grub2-pc",
             "kernel",
-            "selinux-policy-targeted",
-            "cloud-init",
+            "langpacks-en",
             "qemu-guest-agent",
+            "selinux-policy-targeted",
             "spice-vdagent"
           ],
           "exclude_packages": [
@@ -106,8 +107,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro net.ifnames=0"
+          "kernel_opts": "ro net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -128,10 +129,20 @@
       "options": {
         "format": "qcow2",
         "filename": "image.qcow2",
+        "size": 3221225472,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-        "root_fs_type": "xfs",
-        "size": 3221225472
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "xfs",
+              "uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   }
diff --git a/test/cases/rhel82_partitioned_disk.json b/test/cases/rhel82_partitioned_disk.json
index c15713ff6..73b46d1af 100644
--- a/test/cases/rhel82_partitioned_disk.json
+++ b/test/cases/rhel82_partitioned_disk.json
@@ -38,6 +38,7 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "dracut-config-generic",
                 "e2fsprogs",
                 "glibc",
@@ -112,8 +113,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro net.ifnames=0"
+          "kernel_opts": "ro net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -145,9 +146,20 @@
         "format": "raw",
         "filename": "disk.img",
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-        "root_fs_type": "xfs",
-        "size": 3221225472
+        "size": 3221225472,
+        "ptuuid": "0x14fc63d2",
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "xfs",
+              "uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   }
diff --git a/test/cases/rhel82_qcow2.json b/test/cases/rhel82_qcow2.json
index 2382f83b9..f88d17cf1 100644
--- a/test/cases/rhel82_qcow2.json
+++ b/test/cases/rhel82_qcow2.json
@@ -8,7 +8,22 @@
     },
     "filename": "disk.qcow2",
     "output-format": "qcow2",
-    "blueprint": {}
+    "blueprint": {
+      "packages": [
+        {
+          "name": "openssh-server",
+          "version": "*"
+        }
+      ],
+      "customizations": {
+        "user": [
+          {
+            "name": "redhat",
+            "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC61wMCjOSHwbVb4VfVyl5sn497qW4PsdQ7Ty7aD6wDNZ/QjjULkDV/yW5WjDlDQ7UqFH0Sr7vywjqDizUAqK7zM5FsUKsUXWHWwg/ehKg8j9xKcMv11AkFoUoujtfAujnKODkk58XSA9whPr7qcw3vPrmog680pnMSzf9LC7J6kXfs6lkoKfBh9VnlxusCrw2yg0qI1fHAZBLPx7mW6+me71QZsS6sVz8v8KXyrXsKTdnF50FjzHcK9HXDBtSJS5wA3fkcRYymJe0o6WMWNdgSRVpoSiWaHHmFgdMUJaYoCfhXzyl7LtNb3Q+Sveg+tJK7JaRXBLMUllOlJ6ll5Hod"
+          }
+        ]
+      }
+    }
   },
   "pipeline": {
     "build": {
@@ -29,6 +44,7 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "dracut-config-generic",
                 "e2fsprogs",
                 "glibc",
@@ -64,14 +80,15 @@
             }
           ],
           "packages": [
-            "kernel-core",
             "chrony",
             "dracut-config-generic",
+            "grub2-pc",
+            "kernel-core",
+            "langpacks-en",
+            "openssh-server",
             "polkit",
-            "systemd-udev",
             "selinux-policy-targeted",
-            "grub2-pc",
-            "langpacks-en"
+            "systemd-udev"
           ],
           "exclude_packages": [
             "dracut-config-rescue",
@@ -107,8 +124,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro net.ifnames=0"
+          "kernel_opts": "ro net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -117,6 +134,16 @@
           "language": "en_US"
         }
       },
+      {
+        "name": "org.osbuild.users",
+        "options": {
+          "users": {
+            "redhat": {
+              "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC61wMCjOSHwbVb4VfVyl5sn497qW4PsdQ7Ty7aD6wDNZ/QjjULkDV/yW5WjDlDQ7UqFH0Sr7vywjqDizUAqK7zM5FsUKsUXWHWwg/ehKg8j9xKcMv11AkFoUoujtfAujnKODkk58XSA9whPr7qcw3vPrmog680pnMSzf9LC7J6kXfs6lkoKfBh9VnlxusCrw2yg0qI1fHAZBLPx7mW6+me71QZsS6sVz8v8KXyrXsKTdnF50FjzHcK9HXDBtSJS5wA3fkcRYymJe0o6WMWNdgSRVpoSiWaHHmFgdMUJaYoCfhXzyl7LtNb3Q+Sveg+tJK7JaRXBLMUllOlJ6ll5Hod"
+            }
+          }
+        }
+      },
       {
         "name": "org.osbuild.selinux",
         "options": {
@@ -129,10 +156,20 @@
       "options": {
         "format": "qcow2",
         "filename": "disk.qcow2",
+        "size": 3221225472,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-        "root_fs_type": "xfs",
-        "size": 3221225472
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "xfs",
+              "uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   }
diff --git a/test/cases/rhel82_tar.json b/test/cases/rhel82_tar.json
index 647b5aaae..ab39afe7f 100644
--- a/test/cases/rhel82_tar.json
+++ b/test/cases/rhel82_tar.json
@@ -38,6 +38,7 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "dracut-config-generic",
                 "e2fsprogs",
                 "glibc",
@@ -73,13 +74,13 @@
             }
           ],
           "packages": [
-            "policycoreutils",
-            "selinux-policy-targeted",
-            "kernel",
-            "firewalld",
             "chrony",
             "dracut-config-generic",
-            "langpacks-en"
+            "firewalld",
+            "kernel",
+            "langpacks-en",
+            "policycoreutils",
+            "selinux-policy-targeted"
           ],
           "exclude_packages": [
             "dracut-config-rescue",
@@ -98,8 +99,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro net.ifnames=0"
+          "kernel_opts": "ro net.ifnames=0",
+          "legacy": true
         }
       },
       {
diff --git a/test/cases/rhel82_vhd.json b/test/cases/rhel82_vhd.json
index 9291a97a6..cb8dadc35 100644
--- a/test/cases/rhel82_vhd.json
+++ b/test/cases/rhel82_vhd.json
@@ -32,6 +32,7 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "dracut-config-generic",
                 "e2fsprogs",
                 "glibc",
@@ -68,18 +69,18 @@
           ],
           "packages": [
             "@Core",
-            "grub2-pc",
-            "langpacks-en",
-            "dracut-config-generic",
-            "kernel",
-            "selinux-policy-targeted",
-            "chrony",
             "WALinuxAgent",
-            "python3",
-            "net-tools",
+            "chrony",
             "cloud-init",
             "cloud-utils-growpart",
-            "gdisk"
+            "dracut-config-generic",
+            "gdisk",
+            "grub2-pc",
+            "kernel",
+            "langpacks-en",
+            "net-tools",
+            "python3",
+            "selinux-policy-targeted"
           ],
           "exclude_packages": [
             "dracut-config-rescue",
@@ -111,8 +112,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 rootdelay=300 console=ttyS0 earlyprintk=ttyS0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 rootdelay=300 console=ttyS0 earlyprintk=ttyS0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -143,10 +144,20 @@
       "options": {
         "format": "vpc",
         "filename": "image.vhd",
+        "size": 3221225472,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-        "root_fs_type": "xfs",
-        "size": 3221225472
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "xfs",
+              "uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   }
diff --git a/test/cases/rhel82_vmdk.json b/test/cases/rhel82_vmdk.json
index 839a576e4..76eb9b5c7 100644
--- a/test/cases/rhel82_vmdk.json
+++ b/test/cases/rhel82_vmdk.json
@@ -41,6 +41,7 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "dracut-config-generic",
                 "e2fsprogs",
                 "glibc",
@@ -116,8 +117,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro net.ifnames=0"
+          "kernel_opts": "ro net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -148,10 +149,20 @@
       "options": {
         "format": "vmdk",
         "filename": "disk.vmdk",
+        "size": 3221225472,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
-        "root_fs_type": "xfs",
-        "size": 3221225472
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "xfs",
+              "uuid": "0bd700f8-090f-4556-b797-b340297ea1bd",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   }
diff --git a/test/cases/tar_local_boot.json b/test/cases/tar_local_boot.json
index a0cf31a8e..1ffc37dc4 100644
--- a/test/cases/tar_local_boot.json
+++ b/test/cases/tar_local_boot.json
@@ -53,15 +53,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -80,19 +82,20 @@
             }
           ],
           "packages": [
-            "policycoreutils",
-            "selinux-policy-targeted",
-            "kernel",
-            "firewalld",
             "chrony",
+            "firewalld",
+            "kernel",
             "langpacks-en",
-            "openssh-server"
+            "openssh-server",
+            "policycoreutils",
+            "selinux-policy-targeted"
           ],
           "exclude_packages": [
             "dracut-config-rescue"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -105,8 +108,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
diff --git a/test/cases/vhd_empty_blueprint.json b/test/cases/vhd_empty_blueprint.json
index 690c0ca51..a677cfe61 100644
--- a/test/cases/vhd_empty_blueprint.json
+++ b/test/cases/vhd_empty_blueprint.json
@@ -25,15 +25,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -53,21 +55,22 @@
           ],
           "packages": [
             "@Core",
+            "WALinuxAgent",
             "chrony",
-            "kernel",
-            "selinux-policy-targeted",
             "grub2-pc",
+            "kernel",
             "langpacks-en",
+            "libxcrypt-compat",
             "net-tools",
             "ntfsprogs",
-            "WALinuxAgent",
-            "libxcrypt-compat"
+            "selinux-policy-targeted"
           ],
           "exclude_packages": [
             "dracut-config-rescue"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -99,8 +102,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -115,9 +118,20 @@
       "options": {
         "format": "vpc",
         "filename": "image.vhd",
+        "size": 3222274048,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-        "size": 3222274048
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "ext4",
+              "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   },
diff --git a/test/cases/vhd_local_boot.json b/test/cases/vhd_local_boot.json
index 7f29f5349..be24216f0 100644
--- a/test/cases/vhd_local_boot.json
+++ b/test/cases/vhd_local_boot.json
@@ -53,15 +53,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -81,21 +83,22 @@
           ],
           "packages": [
             "@Core",
+            "WALinuxAgent",
             "chrony",
-            "kernel",
-            "selinux-policy-targeted",
             "grub2-pc",
+            "kernel",
             "langpacks-en",
+            "libxcrypt-compat",
             "net-tools",
             "ntfsprogs",
-            "WALinuxAgent",
-            "libxcrypt-compat"
+            "selinux-policy-targeted"
           ],
           "exclude_packages": [
             "dracut-config-rescue"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -123,8 +126,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -165,9 +168,20 @@
       "options": {
         "format": "qcow2",
         "filename": "image.vhd",
+        "size": 3222274048,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-        "size": 3222274048
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "ext4",
+              "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   }
diff --git a/test/cases/vmdk_empty_blueprint.json b/test/cases/vmdk_empty_blueprint.json
index ac7ddf38b..747956fd1 100644
--- a/test/cases/vmdk_empty_blueprint.json
+++ b/test/cases/vmdk_empty_blueprint.json
@@ -25,15 +25,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -65,7 +67,8 @@
             "dracut-config-rescue"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -97,8 +100,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -113,9 +116,20 @@
       "options": {
         "format": "vmdk",
         "filename": "disk.vmdk",
+        "size": 3222274048,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-        "size": 3222274048
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "ext4",
+              "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   },
diff --git a/test/cases/vmdk_local_boot.json b/test/cases/vmdk_local_boot.json
index 74ab98f96..01cb02cfc 100644
--- a/test/cases/vmdk_local_boot.json
+++ b/test/cases/vmdk_local_boot.json
@@ -53,15 +53,17 @@
               ],
               "packages": [
                 "dnf",
+                "dosfstools",
                 "e2fsprogs",
+                "grub2-pc",
                 "policycoreutils",
                 "qemu-img",
                 "systemd",
-                "grub2-pc",
                 "tar"
               ],
               "releasever": "30",
-              "basearch": "x86_64"
+              "basearch": "x86_64",
+              "module_platform_id": "platform:f30"
             }
           }
         ]
@@ -93,7 +95,8 @@
             "dracut-config-rescue"
           ],
           "releasever": "30",
-          "basearch": "x86_64"
+          "basearch": "x86_64",
+          "module_platform_id": "platform:f30"
         }
       },
       {
@@ -121,8 +124,8 @@
         "name": "org.osbuild.grub2",
         "options": {
           "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-          "boot_fs_uuid": "00000000-0000-0000-0000-000000000000",
-          "kernel_opts": "ro biosdevname=0 net.ifnames=0"
+          "kernel_opts": "ro biosdevname=0 net.ifnames=0",
+          "legacy": true
         }
       },
       {
@@ -163,9 +166,20 @@
       "options": {
         "format": "vmdk",
         "filename": "disk.vmdk",
+        "size": 3222274048,
         "ptuuid": "0x14fc63d2",
-        "root_fs_uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
-        "size": 3222274048
+        "pttype": "mbr",
+        "partitions": [
+          {
+            "start": 2048,
+            "bootable": true,
+            "filesystem": {
+              "type": "ext4",
+              "uuid": "76a22bf4-f153-4541-b6c7-0332c0dfaeac",
+              "mountpoint": "/"
+            }
+          }
+        ]
       }
     }
   }
diff --git a/test/run b/test/run
index 070dc01a5..d97e23104 100755
--- a/test/run
+++ b/test/run
@@ -116,10 +116,12 @@ def nspawn_boot_image(image_file, name):
 @contextlib.contextmanager
 def nspawn_boot_archive(image_file, name):
     with tempfile.TemporaryDirectory(dir="/var/tmp") as dir:
-        subprocess.run(["tar", "xf", image_file], cwd=dir)
-        cmd = ["systemd-nspawn", "--boot", "--register=no", "-M", name, "--directory", "."]
+        cmd = ["tar", "xf", image_file]
+        print(f"extracting image to {dir}: {' '.join(cmd)}")
+        subprocess.run(cmd, cwd=dir)
+        cmd = ["systemd-nspawn", "--boot", "--register=no", "-M", name, "--directory", dir]
         print(f"running nspawn command: {' '.join(cmd)}")
-        container = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=dir)
+        container = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
         try:
             yield None
         finally:
@@ -135,12 +137,9 @@ def run_osbuild(pipeline, store):
         osbuild_cmd.append(os.path.abspath(build_env))
 
     result = dict()
-    try:
-        result = json.loads(subprocess.check_output(osbuild_cmd, cwd="./osbuild", encoding="utf-8", input=json.dumps(pipeline)))
-    except subprocess.CalledProcessError as err:
-        print(err.output)
+    result = json.loads(subprocess.check_output(osbuild_cmd, cwd="./osbuild", encoding="utf-8", input=json.dumps(pipeline)))
 
-    return result["tree_id"], result["output_id"]
+    return result.get("output_id")
 
 
 def run_ssh_test(private_key):
@@ -183,7 +182,18 @@ def run_test(case, private_key, store):
         print("skipping this test case, no pipeline given")
         return True
 
-    _, output_id = run_osbuild(case["pipeline"], store)
+    try:
+        output_id = run_osbuild(case["pipeline"], store)
+    except subprocess.CalledProcessError as err:
+        print(err.output)
+        return False
+
+    if output_id == None:
+        print(f"osbuild did not produce an image")
+        return False
+
+    print(f"osbuild successfully built pipeline {output_id}")
+
     filename = os.path.join(store, "refs", output_id, case["compose"]["filename"])
 
     fn, ex = os.path.splitext(filename)