From ff4bf1d872898e26b612a839521c38bca18712ee Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Thu, 22 Feb 2024 10:13:03 +0100 Subject: [PATCH] manifest: add new RawBootcImage type This image type is distinct from the RawOSTreeImage because the way `bootc instal to-filesystem` works is quite different from how our existing ostree deployments work. --- pkg/image/bootc_disk.go | 33 +++++----- pkg/image/bootc_disk_test.go | 39 +++--------- pkg/manifest/pipeline.go | 2 +- pkg/manifest/raw_bootc.go | 115 +++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 48 deletions(-) create mode 100644 pkg/manifest/raw_bootc.go diff --git a/pkg/image/bootc_disk.go b/pkg/image/bootc_disk.go index 021339b8e1..2596e7a6d0 100644 --- a/pkg/image/bootc_disk.go +++ b/pkg/image/bootc_disk.go @@ -8,6 +8,7 @@ import ( "github.com/osbuild/images/pkg/artifact" "github.com/osbuild/images/pkg/container" + "github.com/osbuild/images/pkg/disk" "github.com/osbuild/images/pkg/manifest" "github.com/osbuild/images/pkg/osbuild" "github.com/osbuild/images/pkg/platform" @@ -15,20 +16,22 @@ import ( ) type BootcDiskImage struct { - *OSTreeDiskImage + Base + + Platform platform.Platform + PartitionTable *disk.PartitionTable + + Filename string + + ContainerSource *container.SourceSpec + + Compression string } func NewBootcDiskImage(container container.SourceSpec) *BootcDiskImage { - // XXX: hardcoded for now - ref := "ostree/1/1/0" - return &BootcDiskImage{ - &OSTreeDiskImage{ - Base: NewBase("bootc-raw-image"), - ContainerSource: &container, - Ref: ref, - OSName: "default", - }, + Base: NewBase("bootc-raw-image"), + ContainerSource: &container, } } @@ -54,13 +57,11 @@ func (img *BootcDiskImage) InstantiateManifestFromContainers(m *manifest.Manifes // this is signified by passing nil to the below pipelines. var hostPipeline manifest.Build - opts := &baseRawOstreeImageOpts{useBootupd: true} - baseImage := baseRawOstreeImage(img.OSTreeDiskImage, buildPipeline, opts) + // XXX: no support for customization right now, at least /etc/fstab + // and very basic user (root only?) should be supported + baseImage := manifest.NewRawBootcImage(buildPipeline, containers, img.Platform) + baseImage.PartitionTable = img.PartitionTable - // In BIB, we intend to export multiple images from the same pipeline and - // this is the expected filename for the raw images. Set it so that it's - // always disk.raw even when we're building a qcow2 or other image type. - baseImage.SetFilename("disk.raw") switch imgFormat { case platform.FORMAT_QCOW2: qcow2Pipeline := manifest.NewQCOW2(hostPipeline, baseImage) diff --git a/pkg/image/bootc_disk_test.go b/pkg/image/bootc_disk_test.go index 266aaeda52..90b61a076d 100644 --- a/pkg/image/bootc_disk_test.go +++ b/pkg/image/bootc_disk_test.go @@ -25,7 +25,7 @@ func TestBootcDiskImageNew(t *testing.T) { img := image.NewBootcDiskImage(containerSource) require.NotNil(t, img) - assert.Equal(t, img.OSTreeDiskImage.Base.Name(), "bootc-raw-image") + assert.Equal(t, img.Base.Name(), "bootc-raw-image") } func makeFakeDigest(t *testing.T) string { @@ -73,8 +73,8 @@ func makeBootcDiskImageOsbuildManifest(t *testing.T, opts *bootcDiskImageTestOpt require.Nil(t, err) fakeSourceSpecs := map[string][]container.Spec{ - "build": []container.Spec{{Source: "some-src", Digest: makeFakeDigest(t), ImageID: makeFakeDigest(t)}}, - "ostree-deployment": []container.Spec{{Source: "other-src", Digest: makeFakeDigest(t), ImageID: makeFakeDigest(t)}}, + "build": []container.Spec{{Source: "some-src", Digest: makeFakeDigest(t), ImageID: makeFakeDigest(t)}}, + "image": []container.Spec{{Source: "other-src", Digest: makeFakeDigest(t), ImageID: makeFakeDigest(t)}}, } osbuildManifest, err := m.Serialize(nil, fakeSourceSpecs, nil) @@ -127,37 +127,14 @@ func TestBootcDiskImageInstantiateVmdk(t *testing.T) { require.NotNil(t, pipeline) } -func TestBootcDiskImageUsesBootupd(t *testing.T) { +func TestBootcDiskImageUsesBootcInstallToFs(t *testing.T) { osbuildManifest := makeBootcDiskImageOsbuildManifest(t, nil) - // check that bootupd is part of the "image" pipeline + // check that bootc.install-to-filesystem is part of the "image" pipeline imagePipeline := findPipelineFromOsbuildManifest(t, osbuildManifest, "image") require.NotNil(t, imagePipeline) - bootupdStage := findStageFromOsbuildPipeline(t, imagePipeline, "org.osbuild.bootupd") - require.NotNil(t, bootupdStage) - - // ensure that "grub2" is not part of the ostree pipeline - ostreeDeployPipeline := findPipelineFromOsbuildManifest(t, osbuildManifest, "ostree-deployment") - require.NotNil(t, ostreeDeployPipeline) - grubStage := findStageFromOsbuildPipeline(t, ostreeDeployPipeline, "org.osbuild.grub2") - require.Nil(t, grubStage) -} + bootcStage := findStageFromOsbuildPipeline(t, imagePipeline, "org.osbuild.bootc.install-to-filesystem") + require.NotNil(t, bootcStage) -func TestBootcDiskImageBootupdBiosSupport(t *testing.T) { - for _, withBios := range []bool{false, true} { - osbuildManifest := makeBootcDiskImageOsbuildManifest(t, &bootcDiskImageTestOpts{BIOS: withBios, ImageFormat: platform.FORMAT_QCOW2}) - - imagePipeline := findPipelineFromOsbuildManifest(t, osbuildManifest, "image") - require.NotNil(t, imagePipeline) - bootupdStage := findStageFromOsbuildPipeline(t, imagePipeline, "org.osbuild.bootupd") - require.NotNil(t, bootupdStage) - - opts := bootupdStage["options"].(map[string]interface{}) - if withBios { - biosOpts := opts["bios"].(map[string]interface{}) - assert.Equal(t, biosOpts["device"], "disk") - } else { - require.Nil(t, opts["bios"]) - } - } + // XXX: check customizations here too } diff --git a/pkg/manifest/pipeline.go b/pkg/manifest/pipeline.go index 4a92e6b561..ed1d67e79b 100644 --- a/pkg/manifest/pipeline.go +++ b/pkg/manifest/pipeline.go @@ -97,7 +97,7 @@ func (p Base) getCheckpoint() bool { } func (p *Base) Export() *artifact.Artifact { - panic("can't export pipeline") + panic("can't export pipeline directly from pipeline.Base") } func (p Base) getExport() bool { diff --git a/pkg/manifest/raw_bootc.go b/pkg/manifest/raw_bootc.go new file mode 100644 index 0000000000..d3c6ba2996 --- /dev/null +++ b/pkg/manifest/raw_bootc.go @@ -0,0 +1,115 @@ +package manifest + +import ( + "github.com/osbuild/images/pkg/artifact" + "github.com/osbuild/images/pkg/container" + "github.com/osbuild/images/pkg/disk" + "github.com/osbuild/images/pkg/osbuild" + "github.com/osbuild/images/pkg/ostree" + "github.com/osbuild/images/pkg/platform" + "github.com/osbuild/images/pkg/rpmmd" +) + +// A RawBootcImage represents a raw bootc image file which can be booted in a +// hypervisor. +type RawBootcImage struct { + Base + + filename string + platform platform.Platform + + containers []container.SourceSpec + containerSpecs []container.Spec + + // customizations go here because there is no intermediate + // tree, with `bootc install to-filesystem` we can only work + // with the image itself + PartitionTable *disk.PartitionTable +} + +func (p RawBootcImage) Filename() string { + return p.filename +} + +func (p *RawBootcImage) SetFilename(filename string) { + p.filename = filename +} + +func NewRawBootcImage(buildPipeline Build, containers []container.SourceSpec, platform platform.Platform) *RawBootcImage { + p := &RawBootcImage{ + Base: NewBase("image", buildPipeline), + filename: "disk.img", + platform: platform, + + containers: containers, + } + buildPipeline.addDependent(p) + return p +} + +func (p *RawBootcImage) getContainerSources() []container.SourceSpec { + return p.containers +} + +func (p *RawBootcImage) getContainerSpecs() []container.Spec { + return p.containerSpecs +} + +func (p *RawBootcImage) serializeStart(_ []rpmmd.PackageSpec, containerSpecs []container.Spec, _ []ostree.CommitSpec) { + if len(p.containerSpecs) > 0 { + panic("double call to serializeStart()") + } + p.containerSpecs = containerSpecs +} + +func (p *RawBootcImage) serializeEnd() { + if len(p.containerSpecs) == 0 { + panic("serializeEnd() call when serialization not in progress") + } + p.containerSpecs = nil +} + +func (p *RawBootcImage) serialize() osbuild.Pipeline { + pipeline := p.Base.serialize() + + pt := p.PartitionTable + if pt == nil { + panic("no partition table in live image") + } + + for _, stage := range osbuild.GenImagePrepareStages(pt, p.filename, osbuild.PTSfdisk) { + pipeline.AddStage(stage) + } + + inputs := osbuild.ContainerDeployInputs{ + Images: osbuild.NewContainersInputForSources(p.containerSpecs), + } + devices, mounts, err := osbuild.GenBootupdDevicesMounts(p.filename, p.PartitionTable) + if err != nil { + panic(err) + } + st, err := osbuild.NewBootcInstallToFilesystemStage(inputs, devices, mounts) + if err != nil { + panic(err) + } + pipeline.AddStage(st) + + // XXX: there is no way right now to support any customizations, + // we cannot touch the filesystem after bootc installed it or + // we risk messing with it's selinux labels or future fsverity + // magic. Once we have a mechanism like --copy-etc from + // https://github.com/containers/bootc/pull/267 things should + // be a bit better + + for _, stage := range osbuild.GenImageFinishStages(pt, p.filename) { + pipeline.AddStage(stage) + } + + return pipeline +} + +// XXX: copied from raw.go +func (p *RawBootcImage) Export() *artifact.Artifact { + p.Base.export = true + return artifact.New(p.Name(), p.Filename(), nil) +}