diff --git a/pkg/image/bootc_disk.go b/pkg/image/bootc_disk.go index 08cf81571f..a2c27eb9ea 100644 --- a/pkg/image/bootc_disk.go +++ b/pkg/image/bootc_disk.go @@ -5,26 +5,38 @@ import ( "math/rand" "github.com/osbuild/images/pkg/container" + "github.com/osbuild/images/pkg/customizations/users" + "github.com/osbuild/images/pkg/disk" "github.com/osbuild/images/pkg/manifest" "github.com/osbuild/images/pkg/osbuild" + "github.com/osbuild/images/pkg/platform" "github.com/osbuild/images/pkg/runner" ) type BootcDiskImage struct { - *OSTreeDiskImage + Base + + Platform platform.Platform + PartitionTable *disk.PartitionTable + + Filename string + + ContainerSource *container.SourceSpec + + // Customizations + KernelOptionsAppend []string + + // "Users" is a bit misleading as only root and its ssh key is supported + // right now because that is all that bootc gives us by default but that + // will most likely change over time. + // See https://github.com/containers/bootc/pull/267 + Users []users.User } 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, } } @@ -40,14 +52,17 @@ 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} - - fileBasename := img.Filename + // TODO: no support for customization right now but minimal support + // for root ssh keys is supported + baseImage := manifest.NewRawBootcImage(buildPipeline, containers, img.Platform) + baseImage.PartitionTable = img.PartitionTable + baseImage.Users = img.Users + baseImage.KernelOptionsAppend = img.KernelOptionsAppend // In BIB, we export multiple images from the same pipeline so we use the // filename as the basename for each export and set the extensions based on // each file format. - baseImage := baseRawOstreeImage(img.OSTreeDiskImage, buildPipeline, opts) + fileBasename := img.Filename baseImage.SetFilename(fmt.Sprintf("%s.raw", fileBasename)) qcow2Pipeline := manifest.NewQCOW2(hostPipeline, baseImage) diff --git a/pkg/image/bootc_disk_test.go b/pkg/image/bootc_disk_test.go index 9d2bc0903e..9301d17556 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 { @@ -38,6 +38,8 @@ func makeFakeDigest(t *testing.T) string { type bootcDiskImageTestOpts struct { ImageFormat platform.ImageFormat BIOS bool + + KernelOptionsAppend []string } func makeFakePlatform(opts *bootcDiskImageTestOpts) platform.Platform { @@ -63,9 +65,11 @@ func makeBootcDiskImageOsbuildManifest(t *testing.T, opts *bootcDiskImageTestOpt containers := []container.SourceSpec{containerSource} img := image.NewBootcDiskImage(containerSource) + img.Filename = "fake-disk" require.NotNil(t, img) img.Platform = makeFakePlatform(opts) img.PartitionTable = testdisk.MakeFakePartitionTable("/", "/boot", "/boot/efi") + img.KernelOptionsAppend = opts.KernelOptionsAppend m := &manifest.Manifest{} runi := &runner.Fedora{} @@ -73,8 +77,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,39 +131,32 @@ func TestBootcDiskImageInstantiateVmdk(t *testing.T) { require.NotNil(t, pipeline) } -func TestBootcDiskImageUsesBootupd(t *testing.T) { - osbuildManifest := makeBootcDiskImageOsbuildManifest(t, nil) +func TestBootcDiskImageUsesBootcInstallToFs(t *testing.T) { + opts := &bootcDiskImageTestOpts{ + KernelOptionsAppend: []string{"karg1", "karg2"}, + } + osbuildManifest := makeBootcDiskImageOsbuildManifest(t, opts) - // 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) -} - -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"]) - } + bootcStage := findStageFromOsbuildPipeline(t, imagePipeline, "org.osbuild.bootc.install-to-filesystem") + require.NotNil(t, bootcStage) + + // ensure loopback for the entire disk with partscan is used or install + // to-filesystem will fail + devicesDisk := bootcStage["devices"].(map[string]interface{})["disk"].(map[string]interface{}) + assert.Equal(t, "org.osbuild.loopback", devicesDisk["type"]) + devicesDiskOpts := devicesDisk["options"].(map[string]interface{}) + expectedDiskOpts := map[string]interface{}{ + "partscan": true, + "filename": "fake-disk.raw", } + assert.Equal(t, expectedDiskOpts, devicesDiskOpts) + + // ensure options got passed + bootcOpts := bootcStage["options"].(map[string]interface{}) + assert.Equal(t, []interface{}{"karg1", "karg2"}, bootcOpts["kernel-args"]) } func TestBootcDiskImageExportPipelines(t *testing.T) { diff --git a/pkg/manifest/build.go b/pkg/manifest/build.go index acc7cc189a..ef82d47823 100644 --- a/pkg/manifest/build.go +++ b/pkg/manifest/build.go @@ -235,11 +235,7 @@ func (p *BuildrootFromContainer) serialize() osbuild.Pipeline { pipeline.Runner = p.runner.String() image := osbuild.NewContainersInputForSingleSource(p.containerSpecs[0]) - options := &osbuild.ContainerDeployOptions{ - Exclude: []string{"/sysroot"}, - } - - stage, err := osbuild.NewContainerDeployStage(image, options) + stage, err := osbuild.NewContainerDeployStage(image, &osbuild.ContainerDeployOptions{}) if err != nil { panic(err) } @@ -247,6 +243,7 @@ func (p *BuildrootFromContainer) serialize() osbuild.Pipeline { pipeline.AddStage(osbuild.NewSELinuxStage( &osbuild.SELinuxStageOptions{ FileContexts: "etc/selinux/targeted/contexts/files/file_contexts", + ExcludePaths: []string{"/sysroot"}, Labels: p.getSELinuxLabels(), }, )) diff --git a/pkg/manifest/export_test.go b/pkg/manifest/export_test.go new file mode 100644 index 0000000000..eff4489738 --- /dev/null +++ b/pkg/manifest/export_test.go @@ -0,0 +1,3 @@ +package manifest + +var FindStage = findStage 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..dfe4c2da75 --- /dev/null +++ b/pkg/manifest/raw_bootc.go @@ -0,0 +1,142 @@ +package manifest + +import ( + "fmt" + + "github.com/osbuild/images/pkg/artifact" + "github.com/osbuild/images/pkg/container" + "github.com/osbuild/images/pkg/customizations/users" + "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 + + KernelOptionsAppend []string + + // "Users" is a bit misleading as only root and its ssh key is supported + // right now because that is all that bootc gives us by default but that + // will most likely change over time. + // See https://github.com/containers/bootc/pull/267 + Users []users.User +} + +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(fmt.Errorf("no partition table in live image")) + } + + if len(p.Users) > 1 { + panic(fmt.Errorf("raw bootc image only supports a single root key for user customization, got %v", p.Users)) + } + if len(p.Users) == 1 && p.Users[0].Name != "root" { + panic(fmt.Errorf("raw bootc image only supports the root user, got %v", p.Users)) + } + + for _, stage := range osbuild.GenImagePrepareStages(pt, p.filename, osbuild.PTSfdisk) { + pipeline.AddStage(stage) + } + + if len(p.containerSpecs) != 1 { + panic(fmt.Errorf("expected a single container input got %v", p.containerSpecs)) + } + opts := &osbuild.BootcInstallToFilesystemOptions{ + Kargs: p.KernelOptionsAppend, + } + if len(p.Users) == 1 && p.Users[0].Key != nil { + opts.RootSSHAuthorizedKeys = []string{*p.Users[0].Key} + } + inputs := osbuild.ContainerDeployInputs{ + Images: osbuild.NewContainersInputForSingleSource(p.containerSpecs[0]), + } + devices, mounts, err := osbuild.GenBootupdDevicesMounts(p.filename, p.PartitionTable) + if err != nil { + panic(err) + } + st, err := osbuild.NewBootcInstallToFilesystemStage(opts, 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) +} diff --git a/pkg/manifest/raw_bootc_export_test.go b/pkg/manifest/raw_bootc_export_test.go new file mode 100644 index 0000000000..450bedd34d --- /dev/null +++ b/pkg/manifest/raw_bootc_export_test.go @@ -0,0 +1,20 @@ +package manifest + +import ( + "github.com/osbuild/images/pkg/container" + "github.com/osbuild/images/pkg/osbuild" + "github.com/osbuild/images/pkg/ostree" + "github.com/osbuild/images/pkg/rpmmd" +) + +func (br *BuildrootFromContainer) Dependents() []Pipeline { + return br.dependents +} + +func (rbc *RawBootcImage) Serialize() osbuild.Pipeline { + return rbc.serialize() +} + +func (rbc *RawBootcImage) SerializeStart(a []rpmmd.PackageSpec, b []container.Spec, c []ostree.CommitSpec) { + rbc.serializeStart(a, b, c) +} diff --git a/pkg/manifest/raw_bootc_test.go b/pkg/manifest/raw_bootc_test.go new file mode 100644 index 0000000000..d144654ebb --- /dev/null +++ b/pkg/manifest/raw_bootc_test.go @@ -0,0 +1,113 @@ +package manifest_test + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/osbuild/images/internal/assertx" + "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/internal/testdisk" + "github.com/osbuild/images/pkg/container" + "github.com/osbuild/images/pkg/customizations/users" + "github.com/osbuild/images/pkg/manifest" + "github.com/osbuild/images/pkg/osbuild" + "github.com/osbuild/images/pkg/runner" +) + +func hasPipeline(haystack []manifest.Pipeline, needle manifest.Pipeline) bool { + for _, p := range haystack { + if p == needle { + return true + } + } + return false +} + +func TestNewRawBootcImage(t *testing.T) { + mani := manifest.New() + runner := &runner.Linux{} + buildIf := manifest.NewBuildFromContainer(&mani, runner, nil, nil) + build := buildIf.(*manifest.BuildrootFromContainer) + + rawBootcPipeline := manifest.NewRawBootcImage(build, nil, nil) + require.NotNil(t, rawBootcPipeline) + + assert.True(t, hasPipeline(build.Dependents(), rawBootcPipeline)) + + // disk.img is hardcoded for filename + assert.Equal(t, "disk.img", rawBootcPipeline.Filename()) +} + +func TestRawBootcImageSerialize(t *testing.T) { + mani := manifest.New() + runner := &runner.Linux{} + build := manifest.NewBuildFromContainer(&mani, runner, nil, nil) + + rawBootcPipeline := manifest.NewRawBootcImage(build, nil, nil) + rawBootcPipeline.PartitionTable = testdisk.MakeFakePartitionTable("/", "/boot", "/boot/efi") + rawBootcPipeline.Users = []users.User{{Name: "root", Key: common.ToPtr("some-ssh-key")}} + rawBootcPipeline.KernelOptionsAppend = []string{"karg1", "karg2"} + + rawBootcPipeline.SerializeStart(nil, []container.Spec{{Source: "foo"}}, nil) + imagePipeline := rawBootcPipeline.Serialize() + assert.Equal(t, "image", imagePipeline.Name) + + bootcInst := manifest.FindStage("org.osbuild.bootc.install-to-filesystem", imagePipeline.Stages) + require.NotNil(t, bootcInst) + opts := bootcInst.Options.(*osbuild.BootcInstallToFilesystemOptions) + assert.Equal(t, []string{"some-ssh-key"}, opts.RootSSHAuthorizedKeys) + assert.Equal(t, []string{"karg1", "karg2"}, opts.Kargs) +} + +func TestRawBootcImageSerializeMountsValidated(t *testing.T) { + mani := manifest.New() + runner := &runner.Linux{} + build := manifest.NewBuildFromContainer(&mani, runner, nil, nil) + + rawBootcPipeline := manifest.NewRawBootcImage(build, nil, nil) + // note that we create a partition table without /boot here + rawBootcPipeline.PartitionTable = testdisk.MakeFakePartitionTable("/", "/missing-boot") + rawBootcPipeline.SerializeStart(nil, []container.Spec{{Source: "foo"}}, nil) + assert.PanicsWithError(t, `required mounts for bootupd stage [/boot /boot/efi] missing`, func() { + rawBootcPipeline.Serialize() + }) +} + +func TestRawBootcImageSerializeValidatesUsers(t *testing.T) { + mani := manifest.New() + runner := &runner.Linux{} + build := manifest.NewBuildFromContainer(&mani, runner, nil, nil) + + rawBootcPipeline := manifest.NewRawBootcImage(build, nil, nil) + rawBootcPipeline.PartitionTable = testdisk.MakeFakePartitionTable("/", "/boot", "/boot/efi") + rawBootcPipeline.SerializeStart(nil, []container.Spec{{Source: "foo"}}, nil) + + for _, tc := range []struct { + users []users.User + expectedErr string + }{ + // good + {nil, ""}, + {[]users.User{{Name: "root"}}, ""}, + {[]users.User{{Name: "root", Key: common.ToPtr("some-key")}}, ""}, + // bad + {[]users.User{{Name: "foo"}}, + "raw bootc image only supports the root user, got.*"}, + {[]users.User{{Name: "root"}, {Name: "foo"}}, + "raw bootc image only supports a single root key for user customization, got.*"}, + } { + rawBootcPipeline.Users = tc.users + + if tc.expectedErr == "" { + rawBootcPipeline.Serialize() + } else { + expectedErr := regexp.MustCompile(tc.expectedErr) + assertx.PanicsWithErrorRegexp(t, expectedErr, func() { + rawBootcPipeline.Serialize() + }) + } + } +} diff --git a/pkg/osbuild/bootc_install_to_filesystem_stage.go b/pkg/osbuild/bootc_install_to_filesystem_stage.go index 26402a2bd5..06be586016 100644 --- a/pkg/osbuild/bootc_install_to_filesystem_stage.go +++ b/pkg/osbuild/bootc_install_to_filesystem_stage.go @@ -4,6 +4,15 @@ import ( "fmt" ) +type BootcInstallToFilesystemOptions struct { + // options for --root-ssh-authorized-keys + RootSSHAuthorizedKeys []string `json:"root-ssh-authorized-keys,omitempty"` + // options for --karg + Kargs []string `json:"kernel-args,omitempty"` +} + +func (BootcInstallToFilesystemOptions) isStageOptions() {} + // NewBootcInstallToFilesystem creates a new stage for the // org.osbuild.bootc.install-to-filesystem stage. // @@ -12,17 +21,18 @@ import ( // bootc/bootupd find and install all required bootloader bits. // // The mounts input should be generated with GenBootupdDevicesMounts. -func NewBootcInstallToFilesystemStage(inputs ContainersInput, devices map[string]Device, mounts []Mount) (*Stage, error) { +func NewBootcInstallToFilesystemStage(options *BootcInstallToFilesystemOptions, inputs ContainerDeployInputs, devices map[string]Device, mounts []Mount) (*Stage, error) { if err := validateBootupdMounts(mounts); err != nil { return nil, err } - if len(inputs.References) != 1 { - return nil, fmt.Errorf("expected exactly one container input but got: %v (%v)", len(inputs.References), inputs.References) + if len(inputs.Images.References) != 1 { + return nil, fmt.Errorf("expected exactly one container input but got: %v (%v)", len(inputs.Images.References), inputs.Images.References) } return &Stage{ Type: "org.osbuild.bootc.install-to-filesystem", + Options: options, Inputs: inputs, Devices: devices, Mounts: mounts, diff --git a/pkg/osbuild/bootc_install_to_filesystem_stage_test.go b/pkg/osbuild/bootc_install_to_filesystem_stage_test.go index cbac772583..3f6abf1a19 100644 --- a/pkg/osbuild/bootc_install_to_filesystem_stage_test.go +++ b/pkg/osbuild/bootc_install_to_filesystem_stage_test.go @@ -11,14 +11,17 @@ import ( "github.com/osbuild/images/pkg/osbuild" ) -func makeFakeContainerInputs() osbuild.ContainersInput { - return osbuild.NewContainersInputForSources([]container.Spec{ - { - ImageID: "id-0", - Source: "registry.example.org/reg/img", - LocalName: "local-name", +func makeFakeContainerInputs() osbuild.ContainerDeployInputs { + return osbuild.ContainerDeployInputs{ + Images: osbuild.NewContainersInputForSources([]container.Spec{ + { + ImageID: "id-0", + Source: "registry.example.org/reg/img", + LocalName: "local-name", + }, }, - }) + ), + } } func TestBootcInstallToFilesystemStageNewHappy(t *testing.T) { @@ -28,11 +31,12 @@ func TestBootcInstallToFilesystemStageNewHappy(t *testing.T) { expectedStage := &osbuild.Stage{ Type: "org.osbuild.bootc.install-to-filesystem", + Options: (*osbuild.BootcInstallToFilesystemOptions)(nil), Inputs: inputs, Devices: devices, Mounts: mounts, } - stage, err := osbuild.NewBootcInstallToFilesystemStage(inputs, devices, mounts) + stage, err := osbuild.NewBootcInstallToFilesystemStage(nil, inputs, devices, mounts) require.Nil(t, err) assert.Equal(t, stage, expectedStage) } @@ -40,23 +44,25 @@ func TestBootcInstallToFilesystemStageNewHappy(t *testing.T) { func TestBootcInstallToFilesystemStageNewNoContainers(t *testing.T) { devices := makeOsbuildDevices("dev-for-/", "dev-for-/boot", "dev-for-/boot/efi") mounts := makeOsbuildMounts("/", "/boot", "/boot/efi") - inputs := osbuild.ContainersInput{} + inputs := osbuild.ContainerDeployInputs{} - _, err := osbuild.NewBootcInstallToFilesystemStage(inputs, devices, mounts) + _, err := osbuild.NewBootcInstallToFilesystemStage(nil, inputs, devices, mounts) assert.EqualError(t, err, "expected exactly one container input but got: 0 (map[])") } func TestBootcInstallToFilesystemStageNewTwoContainers(t *testing.T) { devices := makeOsbuildDevices("dev-for-/", "dev-for-/boot", "dev-for-/boot/efi") mounts := makeOsbuildMounts("/", "/boot", "/boot/efi") - inputs := osbuild.ContainersInput{ - References: map[string]osbuild.ContainersInputSourceRef{ - "1": {}, - "2": {}, + inputs := osbuild.ContainerDeployInputs{ + Images: osbuild.ContainersInput{ + References: map[string]osbuild.ContainersInputSourceRef{ + "1": {}, + "2": {}, + }, }, } - _, err := osbuild.NewBootcInstallToFilesystemStage(inputs, devices, mounts) + _, err := osbuild.NewBootcInstallToFilesystemStage(nil, inputs, devices, mounts) assert.EqualError(t, err, "expected exactly one container input but got: 2 (map[1:{} 2:{}])") } @@ -65,7 +71,7 @@ func TestBootcInstallToFilesystemStageMissingMounts(t *testing.T) { mounts := makeOsbuildMounts("/") inputs := makeFakeContainerInputs() - stage, err := osbuild.NewBootcInstallToFilesystemStage(inputs, devices, mounts) + stage, err := osbuild.NewBootcInstallToFilesystemStage(nil, inputs, devices, mounts) // XXX: rename error assert.ErrorContains(t, err, "required mounts for bootupd stage [/boot /boot/efi] missing") require.Nil(t, stage) @@ -76,21 +82,24 @@ func TestBootcInstallToFilesystemStageJsonHappy(t *testing.T) { mounts := makeOsbuildMounts("/", "/boot", "/boot/efi") inputs := makeFakeContainerInputs() - stage, err := osbuild.NewBootcInstallToFilesystemStage(inputs, devices, mounts) + stage, err := osbuild.NewBootcInstallToFilesystemStage(nil, inputs, devices, mounts) require.Nil(t, err) stageJson, err := json.MarshalIndent(stage, "", " ") require.Nil(t, err) assert.Equal(t, string(stageJson), `{ "type": "org.osbuild.bootc.install-to-filesystem", "inputs": { - "type": "org.osbuild.containers", - "origin": "org.osbuild.source", - "references": { - "id-0": { - "name": "local-name" + "images": { + "type": "org.osbuild.containers", + "origin": "org.osbuild.source", + "references": { + "id-0": { + "name": "local-name" + } } } }, + "options": null, "devices": { "dev-for-/": { "type": "org.osbuild.loopback" diff --git a/pkg/osbuild/container_deploy_stage.go b/pkg/osbuild/container_deploy_stage.go index 55d510b921..be7606b2f9 100644 --- a/pkg/osbuild/container_deploy_stage.go +++ b/pkg/osbuild/container_deploy_stage.go @@ -9,7 +9,7 @@ type ContainerDeployInputs struct { func (ContainerDeployInputs) isStageInputs() {} type ContainerDeployOptions struct { - Exclude []string `json:"exclude"` + Exclude []string `json:"exclude,omitempty"` } func (ContainerDeployOptions) isStageOptions() {} diff --git a/pkg/osbuild/container_deploy_stage_test.go b/pkg/osbuild/container_deploy_stage_test.go index 08af04ad93..3dc0a0d1f9 100644 --- a/pkg/osbuild/container_deploy_stage_test.go +++ b/pkg/osbuild/container_deploy_stage_test.go @@ -70,6 +70,14 @@ func TestContainersDeployStageOptionsJson(t *testing.T) { assert.Equal(t, string(json), expectedJson) } +func TestContainersDeployStageEmptyOptionsJson(t *testing.T) { + expectedJson := `{}` + cdi := osbuild.ContainerDeployOptions{} + json, err := json.MarshalIndent(cdi, "", " ") + require.Nil(t, err) + assert.Equal(t, string(json), expectedJson) +} + func TestContainersDeployStageInputsValidate(t *testing.T) { type testCase struct { inputs osbuild.ContainerDeployInputs diff --git a/pkg/osbuild/selinux_stage.go b/pkg/osbuild/selinux_stage.go index d5b0512cef..196f0fe0ce 100644 --- a/pkg/osbuild/selinux_stage.go +++ b/pkg/osbuild/selinux_stage.go @@ -6,6 +6,7 @@ package osbuild // the filesystem labels to apply to the image. type SELinuxStageOptions struct { FileContexts string `json:"file_contexts"` + ExcludePaths []string `json:"exclude_paths,omitempty"` Labels map[string]string `json:"labels,omitempty"` ForceAutorelabel *bool `json:"force_autorelabel,omitempty"` }