From 5cfc45daa1e75d8cf0a93a1b53e1882f442ee8fd Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Fri, 20 Dec 2024 08:38:20 -0800 Subject: [PATCH 1/7] erofs_stage: Add support for osbuild org.osbuild.erofs stage Supports passing the compression type, extended erofs options, and maximum compress physical cluster size. --- pkg/osbuild/erofs_stage.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 pkg/osbuild/erofs_stage.go diff --git a/pkg/osbuild/erofs_stage.go b/pkg/osbuild/erofs_stage.go new file mode 100644 index 0000000000..fafd19bc31 --- /dev/null +++ b/pkg/osbuild/erofs_stage.go @@ -0,0 +1,24 @@ +package osbuild + +type ErofsCompression struct { + Method string `json:"method"` + Level *int `json:"level,omitempty"` +} + +type ErofsStageOptions struct { + Filename string `json:"filename"` + + Compression *ErofsCompression `json:"compression,omitempty"` + ExtendedOptions []string `json:"options,omitempty"` + ClusterSize *int `json:"cluster-size,omitempty"` +} + +func (ErofsStageOptions) isStageOptions() {} + +func NewErofsStage(options *ErofsStageOptions, inputPipeline string) *Stage { + return &Stage{ + Type: "org.osbuild.erofs", + Options: options, + Inputs: NewPipelineTreeInputs("tree", inputPipeline), + } +} From 1ae8c7de1d8f35f652044f118b4cdff3449e6cff Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Fri, 20 Dec 2024 08:46:11 -0800 Subject: [PATCH 2/7] Rename SquashfsCompression to RootfsCompression In preparation for adding erofs support rename the variable since it will be used for more than squashfs. --- pkg/distro/fedora/images.go | 4 ++-- pkg/distro/rhel/images.go | 4 ++-- pkg/image/anaconda_container_installer.go | 6 +++--- pkg/image/anaconda_live_installer.go | 6 +++--- pkg/image/anaconda_ostree_installer.go | 6 +++--- pkg/image/anaconda_tar_installer.go | 6 +++--- pkg/manifest/anaconda_installer_iso_tree.go | 6 +++--- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pkg/distro/fedora/images.go b/pkg/distro/fedora/images.go index 8b1be8b618..f91342114c 100644 --- a/pkg/distro/fedora/images.go +++ b/pkg/distro/fedora/images.go @@ -479,7 +479,7 @@ func imageInstallerImage(workload workload.Workload, img.Filename = t.Filename() - img.SquashfsCompression = "lz4" + img.RootfsCompression = "lz4" if common.VersionGreaterThanOrEqual(img.OSVersion, VERSION_ROOTFS_SQUASHFS) { img.RootfsType = manifest.SquashfsRootfs } @@ -680,7 +680,7 @@ func iotInstallerImage(workload workload.Workload, img.Filename = t.Filename() - img.SquashfsCompression = "lz4" + img.RootfsCompression = "lz4" if common.VersionGreaterThanOrEqual(img.OSVersion, VERSION_ROOTFS_SQUASHFS) { img.RootfsType = manifest.SquashfsRootfs } diff --git a/pkg/distro/rhel/images.go b/pkg/distro/rhel/images.go index e043250019..e6fb7bb9b8 100644 --- a/pkg/distro/rhel/images.go +++ b/pkg/distro/rhel/images.go @@ -495,7 +495,7 @@ func EdgeInstallerImage(workload workload.Workload, // kickstart though kickstart does support setting them img.Kickstart.Timezone, _ = customizations.GetTimezoneSettings() - img.SquashfsCompression = "xz" + img.RootfsCompression = "xz" if t.Arch().Distro().Releasever() == "10" { img.RootfsType = manifest.SquashfsRootfs } @@ -716,7 +716,7 @@ func ImageInstallerImage(workload workload.Workload, } img.AdditionalAnacondaModules = append(img.AdditionalAnacondaModules, anaconda.ModuleUsers) - img.SquashfsCompression = "xz" + img.RootfsCompression = "xz" if t.Arch().Distro().Releasever() == "10" { img.RootfsType = manifest.SquashfsRootfs } diff --git a/pkg/image/anaconda_container_installer.go b/pkg/image/anaconda_container_installer.go index 4d7b177d3f..d9bae54b40 100644 --- a/pkg/image/anaconda_container_installer.go +++ b/pkg/image/anaconda_container_installer.go @@ -22,8 +22,8 @@ type AnacondaContainerInstaller struct { Platform platform.Platform ExtraBasePackages rpmmd.PackageSet - SquashfsCompression string - RootfsType manifest.RootfsType + RootfsCompression string + RootfsType manifest.RootfsType ISOLabel string Product string @@ -132,7 +132,7 @@ func (img *AnacondaContainerInstaller) InstantiateManifest(m *manifest.Manifest, isoTreePipeline.Release = img.Release isoTreePipeline.Kickstart = img.Kickstart - isoTreePipeline.SquashfsCompression = img.SquashfsCompression + isoTreePipeline.RootfsCompression = img.RootfsCompression // For ostree installers, always put the kickstart file in the root of the ISO isoTreePipeline.PayloadPath = "/container" diff --git a/pkg/image/anaconda_live_installer.go b/pkg/image/anaconda_live_installer.go index 77738d98c2..e897aaf00f 100644 --- a/pkg/image/anaconda_live_installer.go +++ b/pkg/image/anaconda_live_installer.go @@ -23,8 +23,8 @@ type AnacondaLiveInstaller struct { ExtraBasePackages rpmmd.PackageSet - SquashfsCompression string - RootfsType manifest.RootfsType + RootfsCompression string + RootfsType manifest.RootfsType ISOLabel string Product string @@ -107,7 +107,7 @@ func (img *AnacondaLiveInstaller) InstantiateManifest(m *manifest.Manifest, isoTreePipeline.KernelOpts = kernelOpts isoTreePipeline.ISOLinux = isoLinuxEnabled - isoTreePipeline.SquashfsCompression = img.SquashfsCompression + isoTreePipeline.RootfsCompression = img.RootfsCompression isoPipeline := manifest.NewISO(buildPipeline, isoTreePipeline, img.ISOLabel) isoPipeline.SetFilename(img.Filename) diff --git a/pkg/image/anaconda_ostree_installer.go b/pkg/image/anaconda_ostree_installer.go index fc53cc25d8..53e9604206 100644 --- a/pkg/image/anaconda_ostree_installer.go +++ b/pkg/image/anaconda_ostree_installer.go @@ -28,8 +28,8 @@ type AnacondaOSTreeInstaller struct { // Subscription options to include Subscription *subscription.ImageOptions - SquashfsCompression string - RootfsType manifest.RootfsType + RootfsCompression string + RootfsType manifest.RootfsType ISOLabel string Product string @@ -139,7 +139,7 @@ func (img *AnacondaOSTreeInstaller) InstantiateManifest(m *manifest.Manifest, isoTreePipeline.PartitionTable = efiBootPartitionTable(rng) isoTreePipeline.Release = img.Release isoTreePipeline.Kickstart = img.Kickstart - isoTreePipeline.SquashfsCompression = img.SquashfsCompression + isoTreePipeline.RootfsCompression = img.RootfsCompression isoTreePipeline.PayloadPath = "/ostree/repo" diff --git a/pkg/image/anaconda_tar_installer.go b/pkg/image/anaconda_tar_installer.go index ad3d3b5c59..783f17ccfb 100644 --- a/pkg/image/anaconda_tar_installer.go +++ b/pkg/image/anaconda_tar_installer.go @@ -56,8 +56,8 @@ type AnacondaTarInstaller struct { ISORootKickstart bool Kickstart *kickstart.Options - SquashfsCompression string - RootfsType manifest.RootfsType + RootfsCompression string + RootfsType manifest.RootfsType ISOLabel string Product string @@ -195,7 +195,7 @@ func (img *AnacondaTarInstaller) InstantiateManifest(m *manifest.Manifest, isoTreePipeline.Kickstart.Path = img.Kickstart.Path } - isoTreePipeline.SquashfsCompression = img.SquashfsCompression + isoTreePipeline.RootfsCompression = img.RootfsCompression isoTreePipeline.OSPipeline = osPipeline isoTreePipeline.KernelOpts = img.AdditionalKernelOpts diff --git a/pkg/manifest/anaconda_installer_iso_tree.go b/pkg/manifest/anaconda_installer_iso_tree.go index 9240e2d1a5..dc074eddf2 100644 --- a/pkg/manifest/anaconda_installer_iso_tree.go +++ b/pkg/manifest/anaconda_installer_iso_tree.go @@ -49,7 +49,7 @@ type AnacondaInstallerISOTree struct { isoLabel string - SquashfsCompression string + RootfsCompression string OSPipeline *OS OSTreeCommitSource *ostree.SourceSpec @@ -273,8 +273,8 @@ func (p *AnacondaInstallerISOTree) serialize() osbuild.Pipeline { } } - if p.SquashfsCompression != "" { - squashfsOptions.Compression.Method = p.SquashfsCompression + if p.RootfsCompression != "" { + squashfsOptions.Compression.Method = p.RootfsCompression } else { // default to xz if not specified squashfsOptions.Compression.Method = "xz" From a3760a751812f9890f77783da02438975a8ab9bf Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Fri, 20 Dec 2024 09:09:58 -0800 Subject: [PATCH 3/7] manifest: Add RootfsType to the AnacondaInstallerISOTree This will make it easier to select other rootfs types without depending on side-effects like rootfsPipeline being nil. --- pkg/image/anaconda_container_installer.go | 1 + pkg/image/anaconda_live_installer.go | 1 + pkg/image/anaconda_ostree_installer.go | 1 + pkg/image/anaconda_tar_installer.go | 1 + pkg/manifest/anaconda_installer_iso_tree.go | 3 ++- 5 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/image/anaconda_container_installer.go b/pkg/image/anaconda_container_installer.go index d9bae54b40..1eef7c25b0 100644 --- a/pkg/image/anaconda_container_installer.go +++ b/pkg/image/anaconda_container_installer.go @@ -133,6 +133,7 @@ func (img *AnacondaContainerInstaller) InstantiateManifest(m *manifest.Manifest, isoTreePipeline.Kickstart = img.Kickstart isoTreePipeline.RootfsCompression = img.RootfsCompression + isoTreePipeline.RootfsType = img.RootfsType // For ostree installers, always put the kickstart file in the root of the ISO isoTreePipeline.PayloadPath = "/container" diff --git a/pkg/image/anaconda_live_installer.go b/pkg/image/anaconda_live_installer.go index e897aaf00f..b51d6d06c1 100644 --- a/pkg/image/anaconda_live_installer.go +++ b/pkg/image/anaconda_live_installer.go @@ -108,6 +108,7 @@ func (img *AnacondaLiveInstaller) InstantiateManifest(m *manifest.Manifest, isoTreePipeline.ISOLinux = isoLinuxEnabled isoTreePipeline.RootfsCompression = img.RootfsCompression + isoTreePipeline.RootfsType = img.RootfsType isoPipeline := manifest.NewISO(buildPipeline, isoTreePipeline, img.ISOLabel) isoPipeline.SetFilename(img.Filename) diff --git a/pkg/image/anaconda_ostree_installer.go b/pkg/image/anaconda_ostree_installer.go index 53e9604206..a5587c2421 100644 --- a/pkg/image/anaconda_ostree_installer.go +++ b/pkg/image/anaconda_ostree_installer.go @@ -140,6 +140,7 @@ func (img *AnacondaOSTreeInstaller) InstantiateManifest(m *manifest.Manifest, isoTreePipeline.Release = img.Release isoTreePipeline.Kickstart = img.Kickstart isoTreePipeline.RootfsCompression = img.RootfsCompression + isoTreePipeline.RootfsType = img.RootfsType isoTreePipeline.PayloadPath = "/ostree/repo" diff --git a/pkg/image/anaconda_tar_installer.go b/pkg/image/anaconda_tar_installer.go index 783f17ccfb..632b23d570 100644 --- a/pkg/image/anaconda_tar_installer.go +++ b/pkg/image/anaconda_tar_installer.go @@ -196,6 +196,7 @@ func (img *AnacondaTarInstaller) InstantiateManifest(m *manifest.Manifest, } isoTreePipeline.RootfsCompression = img.RootfsCompression + isoTreePipeline.RootfsType = img.RootfsType isoTreePipeline.OSPipeline = osPipeline isoTreePipeline.KernelOpts = img.AdditionalKernelOpts diff --git a/pkg/manifest/anaconda_installer_iso_tree.go b/pkg/manifest/anaconda_installer_iso_tree.go index dc074eddf2..03ddac1ec7 100644 --- a/pkg/manifest/anaconda_installer_iso_tree.go +++ b/pkg/manifest/anaconda_installer_iso_tree.go @@ -50,6 +50,7 @@ type AnacondaInstallerISOTree struct { isoLabel string RootfsCompression string + RootfsType RootfsType OSPipeline *OS OSTreeCommitSource *ostree.SourceSpec @@ -289,7 +290,7 @@ func (p *AnacondaInstallerISOTree) serialize() osbuild.Pipeline { // The iso's rootfs can either be an ext4 filesystem compressed with squashfs, or // a squashfs of the plain directory tree var squashfsStage *osbuild.Stage - if p.rootfsPipeline != nil { + if p.RootfsType == SquashfsExt4Rootfs { squashfsStage = osbuild.NewSquashfsStage(&squashfsOptions, p.rootfsPipeline.Name()) } else { squashfsStage = osbuild.NewSquashfsStage(&squashfsOptions, p.anacondaPipeline.Name()) From d3c20e6e205133b226ca4b0416aa0ba27155210e Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Fri, 20 Dec 2024 09:15:13 -0800 Subject: [PATCH 4/7] manifest: Move squashfs stage creation into NewSquashfsStage --- pkg/manifest/anaconda_installer_iso_tree.go | 72 +++++++++++---------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/pkg/manifest/anaconda_installer_iso_tree.go b/pkg/manifest/anaconda_installer_iso_tree.go index 03ddac1ec7..c837fa6cca 100644 --- a/pkg/manifest/anaconda_installer_iso_tree.go +++ b/pkg/manifest/anaconda_installer_iso_tree.go @@ -155,6 +155,42 @@ func (p *AnacondaInstallerISOTree) getBuildPackages(_ Distro) []string { return packages } +// NewSquashfsStage returns an osbuild stage configured to build +// the squashfs root filesystem for the ISO. +func (p *AnacondaInstallerISOTree) NewSquashfsStage() *osbuild.Stage { + var squashfsOptions osbuild.SquashfsStageOptions + + if p.anacondaPipeline.Type == AnacondaInstallerTypePayload { + squashfsOptions = osbuild.SquashfsStageOptions{ + Filename: "images/install.img", + } + } else if p.anacondaPipeline.Type == AnacondaInstallerTypeLive { + squashfsOptions = osbuild.SquashfsStageOptions{ + Filename: "LiveOS/squashfs.img", + } + } + + if p.RootfsCompression != "" { + squashfsOptions.Compression.Method = p.RootfsCompression + } else { + // default to xz if not specified + squashfsOptions.Compression.Method = "xz" + } + + if squashfsOptions.Compression.Method == "xz" { + squashfsOptions.Compression.Options = &osbuild.FSCompressionOptions{ + BCJ: osbuild.BCJOption(p.anacondaPipeline.platform.GetArch().String()), + } + } + + // The iso's rootfs can either be an ext4 filesystem compressed with squashfs, or + // a squashfs of the plain directory tree + if p.RootfsType == SquashfsExt4Rootfs && p.rootfsPipeline != nil { + return osbuild.NewSquashfsStage(&squashfsOptions, p.rootfsPipeline.Name()) + } + return osbuild.NewSquashfsStage(&squashfsOptions, p.anacondaPipeline.Name()) +} + func (p *AnacondaInstallerISOTree) serializeStart(_ []rpmmd.PackageSpec, containers []container.Spec, commits []ostree.CommitSpec, _ []rpmmd.RepoConfig) { if p.ostreeCommitSpec != nil || p.containerSpec != nil { panic("double call to serializeStart()") @@ -261,41 +297,7 @@ func (p *AnacondaInstallerISOTree) serialize() osbuild.Pipeline { copyStageInputs := osbuild.NewPipelineTreeInputs(inputName, p.anacondaPipeline.Name()) copyStage := osbuild.NewCopyStageSimple(copyStageOptions, copyStageInputs) pipeline.AddStage(copyStage) - - var squashfsOptions osbuild.SquashfsStageOptions - - if p.anacondaPipeline.Type == AnacondaInstallerTypePayload { - squashfsOptions = osbuild.SquashfsStageOptions{ - Filename: "images/install.img", - } - } else if p.anacondaPipeline.Type == AnacondaInstallerTypeLive { - squashfsOptions = osbuild.SquashfsStageOptions{ - Filename: "LiveOS/squashfs.img", - } - } - - if p.RootfsCompression != "" { - squashfsOptions.Compression.Method = p.RootfsCompression - } else { - // default to xz if not specified - squashfsOptions.Compression.Method = "xz" - } - - if squashfsOptions.Compression.Method == "xz" { - squashfsOptions.Compression.Options = &osbuild.FSCompressionOptions{ - BCJ: osbuild.BCJOption(p.anacondaPipeline.platform.GetArch().String()), - } - } - - // The iso's rootfs can either be an ext4 filesystem compressed with squashfs, or - // a squashfs of the plain directory tree - var squashfsStage *osbuild.Stage - if p.RootfsType == SquashfsExt4Rootfs { - squashfsStage = osbuild.NewSquashfsStage(&squashfsOptions, p.rootfsPipeline.Name()) - } else { - squashfsStage = osbuild.NewSquashfsStage(&squashfsOptions, p.anacondaPipeline.Name()) - } - pipeline.AddStage(squashfsStage) + pipeline.AddStage(p.NewSquashfsStage()) if p.ISOLinux { isoLinuxOptions := &osbuild.ISOLinuxStageOptions{ From e644d731adcc287249ab28eabcb3da9e0c92fe30 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Fri, 20 Dec 2024 09:22:08 -0800 Subject: [PATCH 5/7] manifest: Add support for plain erofs root filesystem on the iso Add support for erofs by setting the RootfsType on AnacondaInstallerISOTree to ErofsRootfs to select a plain erofs compressed root filesystem for the Anaconda ISO. The mkfs.erofs arguments will look like this: zstd,8 -E all-fragments,dedupe -C 131072 --- pkg/manifest/anaconda_installer_iso_tree.go | 50 +++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/pkg/manifest/anaconda_installer_iso_tree.go b/pkg/manifest/anaconda_installer_iso_tree.go index c837fa6cca..dafa2f2152 100644 --- a/pkg/manifest/anaconda_installer_iso_tree.go +++ b/pkg/manifest/anaconda_installer_iso_tree.go @@ -23,6 +23,7 @@ type RootfsType uint64 const ( // Rootfs type enum SquashfsExt4Rootfs RootfsType = iota // Create an EXT4 rootfs compressed by Squashfs SquashfsRootfs // Create a plain squashfs rootfs + ErofsRootfs // Create a plain erofs rootfs ) // An AnacondaInstallerISOTree represents a tree containing the anaconda installer, @@ -136,8 +137,13 @@ func (p *AnacondaInstallerISOTree) getInline() []string { return inlineData } func (p *AnacondaInstallerISOTree) getBuildPackages(_ Distro) []string { - packages := []string{ - "squashfs-tools", + var packages []string + switch p.RootfsType { + case SquashfsExt4Rootfs, SquashfsRootfs: + packages = []string{"squashfs-tools"} + case ErofsRootfs: + packages = []string{"erofs-utils"} + default: } if p.OSTreeCommitSource != nil { @@ -191,6 +197,36 @@ func (p *AnacondaInstallerISOTree) NewSquashfsStage() *osbuild.Stage { return osbuild.NewSquashfsStage(&squashfsOptions, p.anacondaPipeline.Name()) } +// NewErofsStage returns an osbuild stage configured to build +// the erofs root filesystem for the ISO. +func (p *AnacondaInstallerISOTree) NewErofsStage() *osbuild.Stage { + var erofsOptions osbuild.ErofsStageOptions + + if p.anacondaPipeline.Type == AnacondaInstallerTypePayload { + erofsOptions = osbuild.ErofsStageOptions{ + Filename: "images/install.img", + } + } else if p.anacondaPipeline.Type == AnacondaInstallerTypeLive { + erofsOptions = osbuild.ErofsStageOptions{ + Filename: "LiveOS/squashfs.img", + } + } + + var compression osbuild.ErofsCompression + if p.RootfsCompression != "" { + compression.Method = p.RootfsCompression + } else { + // default to zstd if not specified + compression.Method = "zstd" + } + compression.Level = common.ToPtr(8) + erofsOptions.Compression = &compression + erofsOptions.ExtendedOptions = []string{"all-fragments", "dedupe"} + erofsOptions.ClusterSize = common.ToPtr(131072) + + return osbuild.NewErofsStage(&erofsOptions, p.anacondaPipeline.Name()) +} + func (p *AnacondaInstallerISOTree) serializeStart(_ []rpmmd.PackageSpec, containers []container.Spec, commits []ostree.CommitSpec, _ []rpmmd.RepoConfig) { if p.ostreeCommitSpec != nil || p.containerSpec != nil { panic("double call to serializeStart()") @@ -297,7 +333,15 @@ func (p *AnacondaInstallerISOTree) serialize() osbuild.Pipeline { copyStageInputs := osbuild.NewPipelineTreeInputs(inputName, p.anacondaPipeline.Name()) copyStage := osbuild.NewCopyStageSimple(copyStageOptions, copyStageInputs) pipeline.AddStage(copyStage) - pipeline.AddStage(p.NewSquashfsStage()) + + // Add the selected roofs stage + switch p.RootfsType { + case SquashfsExt4Rootfs, SquashfsRootfs: + pipeline.AddStage(p.NewSquashfsStage()) + case ErofsRootfs: + pipeline.AddStage(p.NewErofsStage()) + default: + } if p.ISOLinux { isoLinuxOptions := &osbuild.ISOLinuxStageOptions{ From 77d2a8bc8596ee055a1db0cd5128bf769f25f07a Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Fri, 10 Jan 2025 11:24:20 -0800 Subject: [PATCH 6/7] manifest: Add checks for the rootfs stages Add tests to make sure the squashfs or erofs (without squashfs) stages are present in the manifest when the RootfsType is set. --- .../anaconda_installer_iso_tree_test.go | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/pkg/manifest/anaconda_installer_iso_tree_test.go b/pkg/manifest/anaconda_installer_iso_tree_test.go index 511ae63bff..26ce76828d 100644 --- a/pkg/manifest/anaconda_installer_iso_tree_test.go +++ b/pkg/manifest/anaconda_installer_iso_tree_test.go @@ -4,6 +4,8 @@ import ( "crypto/sha256" "fmt" "math/rand" + "slices" + "strings" "testing" "github.com/osbuild/images/internal/common" @@ -71,6 +73,16 @@ func newTestAnacondaISOTree() *AnacondaInstallerISOTree { return pipeline } +// Helper to return a comma separated string of the stage names +// used to help debug failures +func dumpStages(stages []*osbuild.Stage) string { + var stageNames []string + for _, stage := range stages { + stageNames = append(stageNames, stage.Type) + } + return strings.Join(stageNames, ", ") +} + func checkISOTreeStages(stages []*osbuild.Stage, expected, exclude []string) error { commonStages := []string{ "org.osbuild.mkdir", @@ -83,6 +95,13 @@ func checkISOTreeStages(stages []*osbuild.Stage, expected, exclude []string) err "org.osbuild.discinfo", } + // Remove excluded stages from common + for _, exlStage := range exclude { + if idx := slices.Index(commonStages, exlStage); idx > -1 { + commonStages = slices.Delete(commonStages, idx, idx+1) + } + } + for _, expStage := range append(commonStages, expected...) { if findStage(expStage, stages) == nil { return fmt.Errorf("did not find expected stage: %s", expStage) @@ -404,6 +423,31 @@ func TestAnacondaISOTreeSerializeWithOS(t *testing.T) { pipeline.serializeStart(nil, nil, nil, nil) assert.Panics(t, func() { pipeline.serialize() }) }) + + t.Run("plain+squashfs-rootfs", func(t *testing.T) { + pipeline := newTestAnacondaISOTree() + pipeline.OSPipeline = osPayload + pipeline.RootfsType = SquashfsRootfs + pipeline.serializeStart(nil, nil, nil, nil) + sp := pipeline.serialize() + pipeline.serializeEnd() + assert.NoError(t, checkISOTreeStages(sp.Stages, payloadStages, + append(variantStages, []string{"org.osbuild.kickstart", "org.osbuild.isolinux"}...)), + dumpStages(sp.Stages)) + }) + + t.Run("plain+erofs-rootfs", func(t *testing.T) { + pipeline := newTestAnacondaISOTree() + pipeline.OSPipeline = osPayload + pipeline.RootfsType = ErofsRootfs + pipeline.serializeStart(nil, nil, nil, nil) + sp := pipeline.serialize() + pipeline.serializeEnd() + assert.NoError(t, checkISOTreeStages(sp.Stages, + append(payloadStages, "org.osbuild.erofs"), + append(variantStages, []string{"org.osbuild.kickstart", "org.osbuild.isolinux", "org.osbuild.squashfs"}...)), + dumpStages(sp.Stages)) + }) } func TestAnacondaISOTreeSerializeWithOSTree(t *testing.T) { @@ -524,6 +568,31 @@ func TestAnacondaISOTreeSerializeWithOSTree(t *testing.T) { pipeline.serializeStart(nil, nil, []ostree.CommitSpec{ostreeCommit}, nil) assert.Panics(t, func() { pipeline.serialize() }) }) + + t.Run("plain+squashfs-rootfs", func(t *testing.T) { + pipeline := newTestAnacondaISOTree() + pipeline.RootfsType = SquashfsRootfs + pipeline.Kickstart = &kickstart.Options{Path: testKsPath, OSTree: &kickstart.OSTree{}} + pipeline.serializeStart(nil, nil, []ostree.CommitSpec{ostreeCommit}, nil) + sp := pipeline.serialize() + pipeline.serializeEnd() + assert.NoError(t, checkISOTreeStages(sp.Stages, payloadStages, + append(variantStages, "org.osbuild.isolinux")), + dumpStages(sp.Stages)) + }) + + t.Run("plain+erofs-erofs", func(t *testing.T) { + pipeline := newTestAnacondaISOTree() + pipeline.RootfsType = ErofsRootfs + pipeline.Kickstart = &kickstart.Options{Path: testKsPath, OSTree: &kickstart.OSTree{}} + pipeline.serializeStart(nil, nil, []ostree.CommitSpec{ostreeCommit}, nil) + sp := pipeline.serialize() + pipeline.serializeEnd() + assert.NoError(t, checkISOTreeStages(sp.Stages, + append(payloadStages, "org.osbuild.erofs"), + append(variantStages, []string{"org.osbuild.isolinux", "org.osbuild.squashfs"}...)), + dumpStages(sp.Stages)) + }) } func makeFakeContainerPayload() container.Spec { @@ -627,6 +696,31 @@ func TestAnacondaISOTreeSerializeWithContainer(t *testing.T) { assert.NotNil(t, skopeoStage) assert.Equal(t, skopeoStage.Options.(*osbuild.SkopeoStageOptions).RemoveSignatures, common.ToPtr(true)) }) + + t.Run("plain+squashfs-rootfs", func(t *testing.T) { + pipeline := newTestAnacondaISOTree() + pipeline.RootfsType = SquashfsRootfs + pipeline.Kickstart = &kickstart.Options{Path: testKsPath} + pipeline.serializeStart(nil, []container.Spec{containerPayload}, nil, nil) + sp := pipeline.serialize() + pipeline.serializeEnd() + assert.NoError(t, checkISOTreeStages(sp.Stages, payloadStages, + append(variantStages, "org.osbuild.isolinux")), + dumpStages(sp.Stages)) + }) + + t.Run("plain+erofs-rootfs", func(t *testing.T) { + pipeline := newTestAnacondaISOTree() + pipeline.RootfsType = ErofsRootfs + pipeline.Kickstart = &kickstart.Options{Path: testKsPath} + pipeline.serializeStart(nil, []container.Spec{containerPayload}, nil, nil) + sp := pipeline.serialize() + pipeline.serializeEnd() + assert.NoError(t, checkISOTreeStages(sp.Stages, + append(payloadStages, "org.osbuild.erofs"), + append(variantStages, []string{"org.osbuild.isolinux", "org.osbuild.squashfs"}...)), + dumpStages(sp.Stages)) + }) } func TestMakeKickstartSudoersPostEmpty(t *testing.T) { From 85183d3e9a184629837cbc9393f1cd71d01bf080 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 13 Jan 2025 09:00:40 +0100 Subject: [PATCH 7/7] osbuild: add minimal json test for the erofs stage This commit adds a minimal json test for the erof stage to ensure that the json struct tags are correct and to also in the "test-as-documentation" sense to hint at what the json looks like. --- pkg/osbuild/erofs_stage_test.go | 83 +++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 pkg/osbuild/erofs_stage_test.go diff --git a/pkg/osbuild/erofs_stage_test.go b/pkg/osbuild/erofs_stage_test.go new file mode 100644 index 0000000000..b86356e3eb --- /dev/null +++ b/pkg/osbuild/erofs_stage_test.go @@ -0,0 +1,83 @@ +package osbuild_test + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/pkg/osbuild" +) + +func TestErofStageJsonMinimal(t *testing.T) { + expectedJson := `{ + "type": "org.osbuild.erofs", + "inputs": { + "tree": { + "type": "org.osbuild.tree", + "origin": "org.osbuild.pipeline", + "references": [ + "name:input-pipeline" + ] + } + }, + "options": { + "filename": "foo.ero" + } +}` + + opts := &osbuild.ErofsStageOptions{ + Filename: "foo.ero", + } + stage := osbuild.NewErofsStage(opts, "input-pipeline") + require.NotNil(t, stage) + + json, err := json.MarshalIndent(stage, "", " ") + require.Nil(t, err) + assert.Equal(t, string(json), expectedJson) +} + +func TestErofStageJsonFull(t *testing.T) { + expectedJson := `{ + "type": "org.osbuild.erofs", + "inputs": { + "tree": { + "type": "org.osbuild.tree", + "origin": "org.osbuild.pipeline", + "references": [ + "name:input-pipeline" + ] + } + }, + "options": { + "filename": "foo.ero", + "compression": { + "method": "lz4hc", + "level": 9 + }, + "options": [ + "all-fragments", + "dedupe" + ], + "cluster-size": 131072 + } +}` + + opts := &osbuild.ErofsStageOptions{ + Filename: "foo.ero", + Compression: &osbuild.ErofsCompression{ + Method: "lz4hc", + Level: common.ToPtr(9), + }, + ExtendedOptions: []string{"all-fragments", "dedupe"}, + ClusterSize: common.ToPtr(131072), + } + stage := osbuild.NewErofsStage(opts, "input-pipeline") + require.NotNil(t, stage) + + json, err := json.MarshalIndent(stage, "", " ") + require.Nil(t, err) + assert.Equal(t, string(json), expectedJson) +}