Repository: osbuild/osbuild-composer
Pull request #2529 (2022-04-07 17:21:40)

Produce VMDK images as stream-optimized
- Support specifying VMDK subformat in `osbuild1` QEMU assembler.
- Change all VMDK image pipelines to produce stream-optimized image.
- Modify Weldr and Cloud APIs to no longer set the `StreamOptimized` variable in `OSBuildJob` structure for VMDK image.

This pull request includes:

- [ ] adequate testing for the new functionality or fixed issue
- [ ] adequate documentation informing people about the change such as
  - [ ] submit a PR for the [guides](https://github.com/osbuild/guides) repository if this PR changed any behavior described there: https://www.osbuild.org/guides/

<!--
Thanks for proposing a change to osbuild-composer!

Please don't remove the above check list. These are things that each pull
request must have before it is merged. It helps maintainers to not forget
anything.

If the reason for ticking any of the boxes is ambiguous, please add a short
note explaining why.

In addition, if this pull request fixes a downstream issue, please refer to
test/README.md and add these additional items:

- [ ] 1st commit of any `rhbz#` related PR contains bug reproducer; CI reports FAIL or
- [ ] PR contains automated tests for new functionality and
- [ ] QE has approved reproducer/new tests and
- [ ] Subsequent commits provide bug fixes without modifying the reproducer; CI reports PASS and
- [ ] QE approves this PR; RHBZ status is set to `MODIFIED + Verified=Tested`

Information regarding our GitLab pipeline (Schutzbot):

CI will not be ran automatically if WIP label is applied or the PR is in DRAFT state, instead only
a link will be provided to the pipeline which can then be triggered manually if desired. To run the
CI automatically either switch the PR to ready or apply WIP+test label.

Outside contributors need manual approval from one of the osbuild-composer maintainers.

Schutzbot will only be triggered if all Tests jobs in GitHub workflow succeed.
-->


thozza: The CI failures are caused by a difference in RPM repos used in osbuild and composer for building packages in CI and also used on SUT on which these packages are being installed.

Blocked on #2508 but also on not-yet-existing changes in osbuild repo to achieve the same thing...
jrusz: Just FYI, got a PR opened in osbuild repo as well https://github.com/osbuild/osbuild/pull/1001

Patch:
diff --git a/cmd/osbuild-worker/jobimpl-osbuild.go b/cmd/osbuild-worker/jobimpl-osbuild.go
index f435de504..4de7f52dd 100644
--- a/cmd/osbuild-worker/jobimpl-osbuild.go
+++ b/cmd/osbuild-worker/jobimpl-osbuild.go
@@ -116,6 +116,7 @@ func validateResult(result *worker.OSBuildJobResult, jobID string) {
 func uploadToS3(a *awscloud.AWS, outputDirectory, exportPath, bucket, key, filename string, osbuildJobResult *worker.OSBuildJobResult, genericS3 bool, streamOptimized bool, streamOptimizedPath string) (err error) {
 	imagePath := path.Join(outputDirectory, exportPath, filename)
 
+	// TODO: delete the stream-optimized handling after "some" time (kept for backward compatibility)
 	// *** SPECIAL VMDK HANDLING START ***
 	// Upload the VMDK image as stream-optimized.
 	// The VMDK conversion is applied only when the job was submitted by Weldr API,
@@ -294,6 +295,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
 		return nil
 	}
 
+	// TODO: delete the stream-optimized handling after "some" time (kept for backward compatibility)
 	streamOptimizedPath := ""
 
 	// NOTE: Currently OSBuild supports multiple exports, but this isn't used
@@ -303,6 +305,7 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
 	if osbuildJobResult.OSBuildOutput.Success && args.ImageName != "" {
 		var f *os.File
 		imagePath := path.Join(outputDirectory, exportPath, args.ImageName)
+		// TODO: delete the stream-optimized handling after "some" time (kept for backward compatibility)
 		if args.StreamOptimized {
 			f, err = vmware.OpenAsStreamOptimizedVmdk(imagePath)
 			if err != nil {
@@ -354,6 +357,15 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
 			// create a symlink so that uploaded image has the name specified by user
 			imageName := args.Targets[0].ImageName + ".vmdk"
 			imagePath := path.Join(tempDirectory, imageName)
+
+			// New version of composer is already generating manifest with stream-optimized VMDK and is not setting
+			// the args.StreamOptimized option. In such case, the image itself is already stream optimized.
+			// Simulate the case as if it was converted by the worker. This makes it simpler to reuse the rest of
+			// the existing code below.
+			if !args.StreamOptimized {
+				streamOptimizedPath = path.Join(outputDirectory, exportPath, options.Filename)
+			}
+
 			err = os.Symlink(streamOptimizedPath, imagePath)
 			if err != nil {
 				osbuildJobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorInvalidConfig, err.Error())
diff --git a/internal/cloudapi/v2/server.go b/internal/cloudapi/v2/server.go
index d14cd0445..a948362c3 100644
--- a/internal/cloudapi/v2/server.go
+++ b/internal/cloudapi/v2/server.go
@@ -102,9 +102,8 @@ func (s *Server) enqueueCompose(distribution distro.Distro, bp blueprint.Bluepri
 	}
 
 	id, err = s.workers.EnqueueOSBuildAsDependency(ir.arch.Name(), &worker.OSBuildJob{
-		Targets:         []*target.Target{ir.target},
-		Exports:         ir.imageType.Exports(),
-		StreamOptimized: ir.imageType.Name() == "vmdk", // https://github.com/osbuild/osbuild/issues/528,
+		Targets: []*target.Target{ir.target},
+		Exports: ir.imageType.Exports(),
 		PipelineNames: &worker.PipelineNames{
 			Build:   ir.imageType.BuildPipelines(),
 			Payload: ir.imageType.PayloadPipelines(),
diff --git a/internal/distro/fedora33/distro.go b/internal/distro/fedora33/distro.go
index 966c49338..c6bb271a8 100644
--- a/internal/distro/fedora33/distro.go
+++ b/internal/distro/fedora33/distro.go
@@ -586,15 +586,16 @@ func (t *imageType) selinuxStageOptions() *osbuild.SELinuxStageOptions {
 	}
 }
 
-func qemuAssembler(format string, filename string, uefi bool, imageSize uint64) *osbuild.Assembler {
+func qemuAssembler(format string, filename string, uefi bool, imageSize uint64, vmdkSubformat osbuild.VMDKSubformat) *osbuild.Assembler {
 	var options osbuild.QEMUAssemblerOptions
 	if uefi {
 		options = osbuild.QEMUAssemblerOptions{
-			Format:   format,
-			Filename: filename,
-			Size:     imageSize,
-			PTUUID:   "8DFDFF87-C96E-EA48-A3A6-9408F1F6B1EF",
-			PTType:   "gpt",
+			Format:        format,
+			VMDKSubformat: vmdkSubformat,
+			Filename:      filename,
+			Size:          imageSize,
+			PTUUID:        "8DFDFF87-C96E-EA48-A3A6-9408F1F6B1EF",
+			PTType:        "gpt",
 			Partitions: []osbuild.QEMUPartition{
 				{
 					Start: 2048,
@@ -621,11 +622,12 @@ func qemuAssembler(format string, filename string, uefi bool, imageSize uint64)
 		}
 	} else {
 		options = osbuild.QEMUAssemblerOptions{
-			Format:   format,
-			Filename: filename,
-			Size:     imageSize,
-			PTUUID:   "0x14fc63d2",
-			PTType:   "mbr",
+			Format:        format,
+			VMDKSubformat: vmdkSubformat,
+			Filename:      filename,
+			Size:          imageSize,
+			PTUUID:        "0x14fc63d2",
+			PTType:        "mbr",
 			Partitions: []osbuild.QEMUPartition{
 				{
 					Start:    2048,
@@ -819,7 +821,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
 		bootable:      true,
 		defaultSize:   6 * GigaByte,
 		assembler: func(uefi bool, options distro.ImageOptions, arch distro.Arch, imageSize uint64) *osbuild.Assembler {
-			return qemuAssembler("raw", "image.raw", uefi, imageSize)
+			return qemuAssembler("raw", "image.raw", uefi, imageSize, "")
 		},
 	}
 
@@ -853,7 +855,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
 		bootable:    true,
 		defaultSize: 2 * GigaByte,
 		assembler: func(uefi bool, options distro.ImageOptions, arch distro.Arch, imageSize uint64) *osbuild.Assembler {
-			return qemuAssembler("qcow2", "disk.qcow2", uefi, imageSize)
+			return qemuAssembler("qcow2", "disk.qcow2", uefi, imageSize, "")
 		},
 	}
 
@@ -887,7 +889,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
 		bootable:    true,
 		defaultSize: 2 * GigaByte,
 		assembler: func(uefi bool, options distro.ImageOptions, arch distro.Arch, imageSize uint64) *osbuild.Assembler {
-			return qemuAssembler("qcow2", "disk.qcow2", uefi, options.Size)
+			return qemuAssembler("qcow2", "disk.qcow2", uefi, options.Size, "")
 		},
 	}
 
@@ -926,7 +928,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
 		bootable:      true,
 		defaultSize:   2 * GigaByte,
 		assembler: func(uefi bool, options distro.ImageOptions, arch distro.Arch, imageSize uint64) *osbuild.Assembler {
-			return qemuAssembler("vpc", "disk.vhd", uefi, imageSize)
+			return qemuAssembler("vpc", "disk.vhd", uefi, imageSize, "")
 		},
 	}
 
@@ -960,7 +962,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
 		bootable:    true,
 		defaultSize: 2 * GigaByte,
 		assembler: func(uefi bool, options distro.ImageOptions, arch distro.Arch, imageSize uint64) *osbuild.Assembler {
-			return qemuAssembler("vmdk", "disk.vmdk", uefi, options.Size)
+			return qemuAssembler("vmdk", "disk.vmdk", uefi, options.Size, osbuild.VMDKSubformatStreamOptimized)
 		},
 	}
 
@@ -994,7 +996,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
 		bootable:    true,
 		defaultSize: 2 * GigaByte,
 		assembler: func(uefi bool, options distro.ImageOptions, arch distro.Arch, imageSize uint64) *osbuild.Assembler {
-			return qemuAssembler("qcow2", "disk.qcow2", uefi, options.Size)
+			return qemuAssembler("qcow2", "disk.qcow2", uefi, options.Size, "")
 		},
 	}
 
diff --git a/internal/distro/rhel8/distro.go b/internal/distro/rhel8/distro.go
index ef7be8674..dd020258b 100644
--- a/internal/distro/rhel8/distro.go
+++ b/internal/distro/rhel8/distro.go
@@ -551,15 +551,16 @@ func (t *imageType) selinuxStageOptions() *osbuild.SELinuxStageOptions {
 	}
 }
 
-func qemuAssembler(format string, filename string, uefi bool, arch distro.Arch, imageSize uint64) *osbuild.Assembler {
+func qemuAssembler(format string, filename string, uefi bool, arch distro.Arch, imageSize uint64, vmdkSubformat osbuild.VMDKSubformat) *osbuild.Assembler {
 	var options osbuild.QEMUAssemblerOptions
 	if uefi {
 		options = osbuild.QEMUAssemblerOptions{
-			Format:   format,
-			Filename: filename,
-			Size:     imageSize,
-			PTUUID:   "8DFDFF87-C96E-EA48-A3A6-9408F1F6B1EF",
-			PTType:   "gpt",
+			Format:        format,
+			VMDKSubformat: vmdkSubformat,
+			Filename:      filename,
+			Size:          imageSize,
+			PTUUID:        "8DFDFF87-C96E-EA48-A3A6-9408F1F6B1EF",
+			PTType:        "gpt",
 			Partitions: []osbuild.QEMUPartition{
 				{
 					Start: 2048,
@@ -589,11 +590,12 @@ func qemuAssembler(format string, filename string, uefi bool, arch distro.Arch,
 					Type:     "grub2",
 					Platform: "powerpc-ieee1275",
 				},
-				Format:   format,
-				Filename: filename,
-				Size:     imageSize,
-				PTUUID:   "0x14fc63d2",
-				PTType:   "dos",
+				Format:        format,
+				VMDKSubformat: vmdkSubformat,
+				Filename:      filename,
+				Size:          imageSize,
+				PTUUID:        "0x14fc63d2",
+				PTType:        "dos",
 				Partitions: []osbuild.QEMUPartition{
 					{
 						Size:     8192,
@@ -615,11 +617,12 @@ func qemuAssembler(format string, filename string, uefi bool, arch distro.Arch,
 				Bootloader: &osbuild.QEMUBootloader{
 					Type: "zipl",
 				},
-				Format:   format,
-				Filename: filename,
-				Size:     imageSize,
-				PTUUID:   "0x14fc63d2",
-				PTType:   "dos",
+				Format:        format,
+				VMDKSubformat: vmdkSubformat,
+				Filename:      filename,
+				Size:          imageSize,
+				PTUUID:        "0x14fc63d2",
+				PTType:        "dos",
 				Partitions: []osbuild.QEMUPartition{
 					{
 						Start:    2048,
@@ -634,11 +637,12 @@ func qemuAssembler(format string, filename string, uefi bool, arch distro.Arch,
 			}
 		} else {
 			options = osbuild.QEMUAssemblerOptions{
-				Format:   format,
-				Filename: filename,
-				Size:     imageSize,
-				PTUUID:   "0x14fc63d2",
-				PTType:   "mbr",
+				Format:        format,
+				VMDKSubformat: vmdkSubformat,
+				Filename:      filename,
+				Size:          imageSize,
+				PTUUID:        "0x14fc63d2",
+				PTType:        "mbr",
 				Partitions: []osbuild.QEMUPartition{
 					{
 						Start:    2048,
@@ -868,7 +872,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
 		bootable:      true,
 		defaultSize:   6 * GigaByte,
 		assembler: func(uefi bool, options distro.ImageOptions, arch distro.Arch, imageSize uint64) *osbuild.Assembler {
-			return qemuAssembler("raw", "image.raw", uefi, arch, imageSize)
+			return qemuAssembler("raw", "image.raw", uefi, arch, imageSize, "")
 		},
 	}
 
@@ -951,7 +955,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
 		bootable:      true,
 		defaultSize:   4 * GigaByte,
 		assembler: func(uefi bool, options distro.ImageOptions, arch distro.Arch, imageSize uint64) *osbuild.Assembler {
-			return qemuAssembler("qcow2", "disk.qcow2", uefi, arch, imageSize)
+			return qemuAssembler("qcow2", "disk.qcow2", uefi, arch, imageSize, "")
 		},
 	}
 
@@ -977,7 +981,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
 		bootable:      true,
 		defaultSize:   4 * GigaByte,
 		assembler: func(uefi bool, options distro.ImageOptions, arch distro.Arch, imageSize uint64) *osbuild.Assembler {
-			return qemuAssembler("qcow2", "disk.qcow2", uefi, arch, imageSize)
+			return qemuAssembler("qcow2", "disk.qcow2", uefi, arch, imageSize, "")
 		},
 	}
 
@@ -1031,7 +1035,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
 		bootable:      true,
 		defaultSize:   4 * GigaByte,
 		assembler: func(uefi bool, options distro.ImageOptions, arch distro.Arch, imageSize uint64) *osbuild.Assembler {
-			return qemuAssembler("vpc", "disk.vhd", uefi, arch, imageSize)
+			return qemuAssembler("vpc", "disk.vhd", uefi, arch, imageSize, "")
 		},
 	}
 
@@ -1058,7 +1062,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
 		bootable:      true,
 		defaultSize:   4 * GigaByte,
 		assembler: func(uefi bool, options distro.ImageOptions, arch distro.Arch, imageSize uint64) *osbuild.Assembler {
-			return qemuAssembler("vmdk", "disk.vmdk", uefi, arch, imageSize)
+			return qemuAssembler("vmdk", "disk.vmdk", uefi, arch, imageSize, osbuild.VMDKSubformatStreamOptimized)
 		},
 	}
 
diff --git a/internal/distro/rhel84/distro.go b/internal/distro/rhel84/distro.go
index 41dbb0a51..caeee47fb 100644
--- a/internal/distro/rhel84/distro.go
+++ b/internal/distro/rhel84/distro.go
@@ -781,12 +781,13 @@ func defaultPartitionTable(imageSize uint64, arch distro.Arch, rng *rand.Rand) d
 	panic("unknown arch: " + arch.Name())
 }
 
-func qemuAssembler(pt *disk.PartitionTable, format string, filename string, imageOptions distro.ImageOptions, arch distro.Arch, qcow2Compat string) *osbuild.Assembler {
+func qemuAssembler(pt *disk.PartitionTable, format string, filename string, imageOptions distro.ImageOptions, arch distro.Arch, qcow2Compat string, vmdkSubformat osbuild.VMDKSubformat) *osbuild.Assembler {
 	options := osbuild.NewQEMUAssemblerOptions(pt)
 
 	options.Format = format
 	options.Filename = filename
 	options.Qcow2Compat = qcow2Compat
+	options.VMDKSubformat = vmdkSubformat
 
 	if arch.Name() == "x86_64" {
 		options.Bootloader = &osbuild.QEMUBootloader{
@@ -1050,7 +1051,7 @@ func newDistro(name, modulePlatformID, ostreeRef string, isCentos bool) distro.D
 		defaultSize:             6 * GigaByte,
 		partitionTableGenerator: defaultPartitionTable,
 		assembler: func(pt *disk.PartitionTable, options distro.ImageOptions, arch distro.Arch) *osbuild.Assembler {
-			return qemuAssembler(pt, "raw", "image.raw", options, arch, "")
+			return qemuAssembler(pt, "raw", "image.raw", options, arch, "", "")
 		},
 	}
 
@@ -1137,7 +1138,7 @@ func newDistro(name, modulePlatformID, ostreeRef string, isCentos bool) distro.D
 		assembler: func(pt *disk.PartitionTable, options distro.ImageOptions, arch distro.Arch) *osbuild.Assembler {
 			// guest images of RHEL 8 must be bootable with older QEMUs.
 			const qcow2Compat = "0.10"
-			return qemuAssembler(pt, "qcow2", "disk.qcow2", options, arch, qcow2Compat)
+			return qemuAssembler(pt, "qcow2", "disk.qcow2", options, arch, qcow2Compat, "")
 		},
 	}
 
@@ -1165,7 +1166,7 @@ func newDistro(name, modulePlatformID, ostreeRef string, isCentos bool) distro.D
 		defaultSize:             4 * GigaByte,
 		partitionTableGenerator: defaultPartitionTable,
 		assembler: func(pt *disk.PartitionTable, options distro.ImageOptions, arch distro.Arch) *osbuild.Assembler {
-			return qemuAssembler(pt, "qcow2", "disk.qcow2", options, arch, "")
+			return qemuAssembler(pt, "qcow2", "disk.qcow2", options, arch, "", "")
 		},
 	}
 
@@ -1224,7 +1225,7 @@ func newDistro(name, modulePlatformID, ostreeRef string, isCentos bool) distro.D
 		defaultSize:             4 * GigaByte,
 		partitionTableGenerator: defaultPartitionTable,
 		assembler: func(pt *disk.PartitionTable, options distro.ImageOptions, arch distro.Arch) *osbuild.Assembler {
-			return qemuAssembler(pt, "vpc", "disk.vhd", options, arch, "")
+			return qemuAssembler(pt, "vpc", "disk.vhd", options, arch, "", "")
 		},
 	}
 
@@ -1253,7 +1254,7 @@ func newDistro(name, modulePlatformID, ostreeRef string, isCentos bool) distro.D
 		defaultSize:             4 * GigaByte,
 		partitionTableGenerator: defaultPartitionTable,
 		assembler: func(pt *disk.PartitionTable, options distro.ImageOptions, arch distro.Arch) *osbuild.Assembler {
-			return qemuAssembler(pt, "vmdk", "disk.vmdk", options, arch, "")
+			return qemuAssembler(pt, "vmdk", "disk.vmdk", options, arch, "", osbuild.VMDKSubformatStreamOptimized)
 		},
 	}
 
diff --git a/internal/distro/rhel85/pipelines.go b/internal/distro/rhel85/pipelines.go
index 6ded547de..51a2cc31b 100644
--- a/internal/distro/rhel85/pipelines.go
+++ b/internal/distro/rhel85/pipelines.go
@@ -129,7 +129,7 @@ func vmdkPipelines(t *imageType, customizations *blueprint.Customizations, optio
 		return nil, err
 	}
 
-	qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, osbuild.QEMUFormatVMDK, nil)
+	qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, osbuild.QEMUFormatVMDK, osbuild.VMDKOptions{Subformat: osbuild.VMDKSubformatStreamOptimized})
 	pipelines = append(pipelines, *qemuPipeline)
 	return pipelines, nil
 }
diff --git a/internal/distro/rhel86/pipelines.go b/internal/distro/rhel86/pipelines.go
index f6ca4ce06..87ba47e16 100644
--- a/internal/distro/rhel86/pipelines.go
+++ b/internal/distro/rhel86/pipelines.go
@@ -103,7 +103,7 @@ func vmdkPipelines(t *imageType, customizations *blueprint.Customizations, optio
 		return nil, err
 	}
 
-	qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, osbuild.QEMUFormatVMDK, nil)
+	qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, osbuild.QEMUFormatVMDK, osbuild.VMDKOptions{Subformat: osbuild.VMDKSubformatStreamOptimized})
 	pipelines = append(pipelines, *qemuPipeline)
 	return pipelines, nil
 }
diff --git a/internal/distro/rhel90/pipelines.go b/internal/distro/rhel90/pipelines.go
index f13d9e3b0..d445f5972 100644
--- a/internal/distro/rhel90/pipelines.go
+++ b/internal/distro/rhel90/pipelines.go
@@ -97,7 +97,7 @@ func vmdkPipelines(t *imageType, customizations *blueprint.Customizations, optio
 	imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, partitionTable, t.arch, kernelVer)
 	pipelines = append(pipelines, *imagePipeline)
 
-	qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, osbuild.QEMUFormatVMDK, nil)
+	qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, osbuild.QEMUFormatVMDK, osbuild.VMDKOptions{Subformat: osbuild.VMDKSubformatStreamOptimized})
 	pipelines = append(pipelines, *qemuPipeline)
 	return pipelines, nil
 }
diff --git a/internal/distro/rhel90beta/pipelines.go b/internal/distro/rhel90beta/pipelines.go
index 6a6fc5429..d32056877 100644
--- a/internal/distro/rhel90beta/pipelines.go
+++ b/internal/distro/rhel90beta/pipelines.go
@@ -121,7 +121,7 @@ func vmdkPipelines(t *imageType, customizations *blueprint.Customizations, optio
 	imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, partitionTable, t.arch, kernelVer)
 	pipelines = append(pipelines, *imagePipeline)
 
-	qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, osbuild.QEMUFormatVMDK, nil)
+	qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, osbuild.QEMUFormatVMDK, osbuild.VMDKOptions{Subformat: osbuild.VMDKSubformatStreamOptimized})
 	pipelines = append(pipelines, *qemuPipeline)
 	return pipelines, nil
 }
diff --git a/internal/osbuild1/qemu_assembler.go b/internal/osbuild1/qemu_assembler.go
index 90eeb01bf..c91ef0866 100644
--- a/internal/osbuild1/qemu_assembler.go
+++ b/internal/osbuild1/qemu_assembler.go
@@ -2,6 +2,16 @@ package osbuild1
 
 import "github.com/osbuild/osbuild-composer/internal/disk"
 
+type VMDKSubformat string
+
+const (
+	VMDKSubformatMonolithicSparse     VMDKSubformat = "monolithicSparse"
+	VMDKSubformatMonolithicFlat       VMDKSubformat = "monolithicFlat"
+	VMDKSubformatTwoGbMaxExtentSparse VMDKSubformat = "twoGbMaxExtentSparse"
+	VMDKSubformatTwoGbMaxExtentFlat   VMDKSubformat = "twoGbMaxExtentFlat"
+	VMDKSubformatStreamOptimized      VMDKSubformat = "streamOptimized"
+)
+
 // QEMUAssemblerOptions desrcibe how to assemble a tree into an image using qemu.
 //
 // The assembler creates an image of the given size, adds a GRUB2 bootloader
@@ -9,14 +19,15 @@ import "github.com/osbuild/osbuild-composer/internal/disk"
 // containing the indicated partitions. Finally, the image is converted into
 // the target format and stored with the given filename.
 type QEMUAssemblerOptions struct {
-	Bootloader  *QEMUBootloader `json:"bootloader,omitempty"`
-	Format      string          `json:"format"`
-	Qcow2Compat string          `json:"qcow2_compat,omitempty"`
-	Filename    string          `json:"filename"`
-	Size        uint64          `json:"size"`
-	PTUUID      string          `json:"ptuuid"`
-	PTType      string          `json:"pttype"`
-	Partitions  []QEMUPartition `json:"partitions"`
+	Bootloader    *QEMUBootloader `json:"bootloader,omitempty"`
+	Format        string          `json:"format"`
+	Qcow2Compat   string          `json:"qcow2_compat,omitempty"`
+	VMDKSubformat VMDKSubformat   `json:"vmdk_subformat,omitempty"`
+	Filename      string          `json:"filename"`
+	Size          uint64          `json:"size"`
+	PTUUID        string          `json:"ptuuid"`
+	PTType        string          `json:"pttype"`
+	Partitions    []QEMUPartition `json:"partitions"`
 }
 
 type QEMUPartition struct {
diff --git a/internal/weldr/api.go b/internal/weldr/api.go
index 6b8fae232..4ab0bdfa8 100644
--- a/internal/weldr/api.go
+++ b/internal/weldr/api.go
@@ -2347,11 +2347,10 @@ func (api *API) composeHandler(writer http.ResponseWriter, request *http.Request
 		var jobId uuid.UUID
 
 		jobId, err = api.workers.EnqueueOSBuild(api.archName, &worker.OSBuildJob{
-			Manifest:        manifest,
-			Targets:         targets,
-			ImageName:       imageType.Filename(),
-			StreamOptimized: imageType.Name() == "vmdk", // https://github.com/osbuild/osbuild/issues/528
-			Exports:         imageType.Exports(),
+			Manifest:  manifest,
+			Targets:   targets,
+			ImageName: imageType.Filename(),
+			Exports:   imageType.Exports(),
 			PipelineNames: &worker.PipelineNames{
 				Build:   imageType.BuildPipelines(),
 				Payload: imageType.PayloadPipelines(),
diff --git a/internal/worker/json.go b/internal/worker/json.go
index 0adb29bd8..293d21c75 100644
--- a/internal/worker/json.go
+++ b/internal/worker/json.go
@@ -15,12 +15,15 @@ import (
 //
 
 type OSBuildJob struct {
-	Manifest        distro.Manifest  `json:"manifest,omitempty"`
-	Targets         []*target.Target `json:"targets,omitempty"`
-	ImageName       string           `json:"image_name,omitempty"`
-	StreamOptimized bool             `json:"stream_optimized,omitempty"`
-	Exports         []string         `json:"export_stages,omitempty"`
-	PipelineNames   *PipelineNames   `json:"pipeline_names,omitempty"`
+	Manifest  distro.Manifest  `json:"manifest,omitempty"`
+	Targets   []*target.Target `json:"targets,omitempty"`
+	ImageName string           `json:"image_name,omitempty"`
+
+	// TODO: Delete this after "some" time (kept for backward compatibility)
+	StreamOptimized bool `json:"stream_optimized,omitempty"`
+
+	Exports       []string       `json:"export_stages,omitempty"`
+	PipelineNames *PipelineNames `json:"pipeline_names,omitempty"`
 }
 
 type JobResult struct {
diff --git a/osbuild-composer.spec b/osbuild-composer.spec
index 8108689e7..673a77946 100644
--- a/osbuild-composer.spec
+++ b/osbuild-composer.spec
@@ -9,7 +9,7 @@
 
 %global goipath         github.com/osbuild/osbuild-composer
 
-Version:        51
+Version:        52
 
 %gometa
 
@@ -326,10 +326,10 @@ The core osbuild-composer binary. This is suitable both for spawning in containe
 Summary:    The worker for osbuild-composer
 Requires:   systemd
 Requires:   qemu-img
-Requires:   osbuild >= 52
-Requires:   osbuild-ostree >= 52
-Requires:   osbuild-lvm2 >= 52
-Requires:   osbuild-luks2 >= 52
+Requires:   osbuild >= 54
+Requires:   osbuild-ostree >= 54
+Requires:   osbuild-lvm2 >= 54
+Requires:   osbuild-luks2 >= 54
 Requires:   %{name}-dnf-json = %{version}-%{release}
 
 # remove in F34


Test patch:
diff --git a/test/data/manifests/centos_8-x86_64-vmdk-boot.json b/test/data/manifests/centos_8-x86_64-vmdk-boot.json
index 5b5283349..dd546837e 100644
--- a/test/data/manifests/centos_8-x86_64-vmdk-boot.json
+++ b/test/data/manifests/centos_8-x86_64-vmdk-boot.json
@@ -5893,7 +5893,8 @@
             "options": {
               "filename": "disk.vmdk",
               "format": {
-                "type": "vmdk"
+                "type": "vmdk",
+                "subformat": "streamOptimized"
               }
             }
           }
diff --git a/test/data/manifests/centos_9-x86_64-vmdk-boot.json b/test/data/manifests/centos_9-x86_64-vmdk-boot.json
index d5ca3554b..473fe03dc 100644
--- a/test/data/manifests/centos_9-x86_64-vmdk-boot.json
+++ b/test/data/manifests/centos_9-x86_64-vmdk-boot.json
@@ -5604,7 +5604,8 @@
             "options": {
               "filename": "disk.vmdk",
               "format": {
-                "type": "vmdk"
+                "type": "vmdk",
+                "subformat": "streamOptimized"
               }
             }
           }
diff --git a/test/data/manifests/fedora_34-x86_64-vmdk-boot.json b/test/data/manifests/fedora_34-x86_64-vmdk-boot.json
index fbea81cfc..4a18cd281 100644
--- a/test/data/manifests/fedora_34-x86_64-vmdk-boot.json
+++ b/test/data/manifests/fedora_34-x86_64-vmdk-boot.json
@@ -3509,6 +3509,7 @@
         "name": "org.osbuild.qemu",
         "options": {
           "format": "vmdk",
+          "vmdk_subformat": "streamOptimized",
           "filename": "disk.vmdk",
           "size": 2147483648,
           "ptuuid": "0x14fc63d2",
diff --git a/test/data/manifests/fedora_35-x86_64-vmdk-boot.json b/test/data/manifests/fedora_35-x86_64-vmdk-boot.json
index cc2d1e77c..b238fcb86 100644
--- a/test/data/manifests/fedora_35-x86_64-vmdk-boot.json
+++ b/test/data/manifests/fedora_35-x86_64-vmdk-boot.json
@@ -3725,6 +3725,7 @@
         "name": "org.osbuild.qemu",
         "options": {
           "format": "vmdk",
+          "vmdk_subformat": "streamOptimized",
           "filename": "disk.vmdk",
           "size": 2147483648,
           "ptuuid": "0x14fc63d2",
diff --git a/test/data/manifests/rhel_8-x86_64-vmdk-boot.json b/test/data/manifests/rhel_8-x86_64-vmdk-boot.json
index fd4a3ac1e..ee392e773 100644
--- a/test/data/manifests/rhel_8-x86_64-vmdk-boot.json
+++ b/test/data/manifests/rhel_8-x86_64-vmdk-boot.json
@@ -3129,6 +3129,7 @@
         "name": "org.osbuild.qemu",
         "options": {
           "format": "vmdk",
+          "vmdk_subformat": "streamOptimized",
           "filename": "disk.vmdk",
           "size": 4294967296,
           "ptuuid": "0x14fc63d2",
diff --git a/test/data/manifests/rhel_84-x86_64-vmdk-boot.json b/test/data/manifests/rhel_84-x86_64-vmdk-boot.json
index ab8035214..64f46533e 100644
--- a/test/data/manifests/rhel_84-x86_64-vmdk-boot.json
+++ b/test/data/manifests/rhel_84-x86_64-vmdk-boot.json
@@ -3482,6 +3482,7 @@
             "type": "grub2"
           },
           "format": "vmdk",
+          "vmdk_subformat": "streamOptimized",
           "filename": "disk.vmdk",
           "size": 4294967296,
           "ptuuid": "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
diff --git a/test/data/manifests/rhel_85-x86_64-vmdk-boot.json b/test/data/manifests/rhel_85-x86_64-vmdk-boot.json
index 14c69b74e..60ae781db 100644
--- a/test/data/manifests/rhel_85-x86_64-vmdk-boot.json
+++ b/test/data/manifests/rhel_85-x86_64-vmdk-boot.json
@@ -2454,7 +2454,8 @@
             "options": {
               "filename": "disk.vmdk",
               "format": {
-                "type": "vmdk"
+                "type": "vmdk",
+                "subformat": "streamOptimized"
               }
             }
           }
diff --git a/test/data/manifests/rhel_86-x86_64-vmdk-boot.json b/test/data/manifests/rhel_86-x86_64-vmdk-boot.json
index 1651d638a..69de42760 100644
--- a/test/data/manifests/rhel_86-x86_64-vmdk-boot.json
+++ b/test/data/manifests/rhel_86-x86_64-vmdk-boot.json
@@ -2480,7 +2480,8 @@
             "options": {
               "filename": "disk.vmdk",
               "format": {
-                "type": "vmdk"
+                "type": "vmdk",
+                "subformat": "streamOptimized"
               }
             }
           }
diff --git a/test/data/manifests/rhel_87-x86_64-vmdk-boot.json b/test/data/manifests/rhel_87-x86_64-vmdk-boot.json
index c7186ce8e..5e9c92ae3 100644
--- a/test/data/manifests/rhel_87-x86_64-vmdk-boot.json
+++ b/test/data/manifests/rhel_87-x86_64-vmdk-boot.json
@@ -2483,7 +2483,8 @@
             "options": {
               "filename": "disk.vmdk",
               "format": {
-                "type": "vmdk"
+                "type": "vmdk",
+                "subformat": "streamOptimized"
               }
             }
           }
diff --git a/test/data/manifests/rhel_90-x86_64-vmdk-boot.json b/test/data/manifests/rhel_90-x86_64-vmdk-boot.json
index 803201b3c..bda2266d1 100644
--- a/test/data/manifests/rhel_90-x86_64-vmdk-boot.json
+++ b/test/data/manifests/rhel_90-x86_64-vmdk-boot.json
@@ -2394,7 +2394,8 @@
             "options": {
               "filename": "disk.vmdk",
               "format": {
-                "type": "vmdk"
+                "type": "vmdk",
+                "subformat": "streamOptimized"
               }
             }
           }
diff --git a/test/data/manifests/rhel_90_beta-x86_64-vmdk-boot.json b/test/data/manifests/rhel_90_beta-x86_64-vmdk-boot.json
index f76fd5383..737ba0ba6 100644
--- a/test/data/manifests/rhel_90_beta-x86_64-vmdk-boot.json
+++ b/test/data/manifests/rhel_90_beta-x86_64-vmdk-boot.json
@@ -2269,7 +2269,8 @@
             "options": {
               "filename": "disk.vmdk",
               "format": {
-                "type": "vmdk"
+                "type": "vmdk",
+                "subformat": "streamOptimized"
               }
             }
           }
diff --git a/test/data/manifests/rhel_91-x86_64-vmdk-boot.json b/test/data/manifests/rhel_91-x86_64-vmdk-boot.json
index 08a815570..e55874024 100644
--- a/test/data/manifests/rhel_91-x86_64-vmdk-boot.json
+++ b/test/data/manifests/rhel_91-x86_64-vmdk-boot.json
@@ -2391,7 +2391,8 @@
             "options": {
               "filename": "disk.vmdk",
               "format": {
-                "type": "vmdk"
+                "type": "vmdk",
+                "subformat": "streamOptimized"
               }
             }
           }