Skip to content

Commit

Permalink
many: use bootupd for image.BootcDiskImage
Browse files Browse the repository at this point in the history
This commit enables bootupd for the `iamge.BootcDiskImage` image
type.
  • Loading branch information
mvo5 committed Jan 17, 2024
1 parent b81b603 commit 0a33801
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 96 deletions.
3 changes: 2 additions & 1 deletion pkg/image/bootc_disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ func (img *BootcDiskImage) InstantiateManifestFromContainers(m *manifest.Manifes
panic(fmt.Sprintf("no compression is allowed with %q format for %q", imgFormat, img.name))
}

baseImage := baseRawOstreeImage(img.OSTreeDiskImage, buildPipeline)
opts := &baseRawOstreeImageOpts{useBootupd: true}
baseImage := baseRawOstreeImage(img.OSTreeDiskImage, buildPipeline, opts)
switch imgFormat {
case platform.FORMAT_QCOW2:
// qcow2 runs without a build pipeline directly from "bib"
Expand Down
116 changes: 98 additions & 18 deletions pkg/image/bootc_disk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,20 @@ func makeFakeDigest(t *testing.T) string {
return "sha256:" + hex.EncodeToString(data[:])
}

func makeFakePlatform() platform.Platform {
type bootcDiskImageTestOpts struct {
BIOS bool
}

func makeFakePlatform(opts *bootcDiskImageTestOpts) platform.Platform {
return &platform.X86{
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
},
BIOS: opts.BIOS,
}
}

func makeFakePartitionTable() *disk.PartitionTable {
func makeFakePartitionTable(opts *bootcDiskImageTestOpts) *disk.PartitionTable {
return &disk.PartitionTable{
Type: "gpt",
Partitions: []disk.Partition{
Expand All @@ -54,11 +59,29 @@ func makeFakePartitionTable() *disk.PartitionTable {
Mountpoint: "/",
},
},
{
Payload: &disk.Filesystem{
Type: "ext4",
UUID: disk.FilesystemDataUUID,
Mountpoint: "/boot",
},
},
{
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
},
},
},
}
}

func TestBootcDiskImageInstantiateNoBuildpipelineForQcow2(t *testing.T) {
func makeBootcDiskImageOsbuildManifest(t *testing.T, opts *bootcDiskImageTestOpts) manifest.OSBuildManifest {
if opts == nil {
opts = &bootcDiskImageTestOpts{}
}

containerSource := container.SourceSpec{
Source: "some-src",
Name: "name",
Expand All @@ -67,35 +90,92 @@ func TestBootcDiskImageInstantiateNoBuildpipelineForQcow2(t *testing.T) {

img := image.NewBootcDiskImage(containerSource)
require.NotNil(t, img)
img.Platform = makeFakePlatform()
img.PartitionTable = makeFakePartitionTable()
img.Platform = makeFakePlatform(opts)
img.PartitionTable = makeFakePartitionTable(opts)

m := &manifest.Manifest{}
runi := &runner.Fedora{}
_, err := img.InstantiateManifestFromContainers(m, containers, runi, nil)
require.Nil(t, err)
sourceSpecs := map[string][]container.Spec{

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)}},
}
osbuildManifest, err := m.Serialize(nil, sourceSpecs, nil)

osbuildManifest, err := m.Serialize(nil, fakeSourceSpecs, nil)
require.Nil(t, err)

return osbuildManifest
}

func findPipelineFromOsbuildManifest(t *testing.T, osbm manifest.OSBuildManifest, pipelineName string) map[string]interface{} {
var mani map[string]interface{}
err = json.Unmarshal(osbuildManifest, &mani)
err := json.Unmarshal(osbm, &mani)
require.Nil(t, err)

pipelines := mani["pipelines"].([]interface{})
findQcowStage := func() map[string]interface{} {
for _, stageIf := range pipelines {
stage := stageIf.(map[string]interface{})
if stage["name"].(string) == "qcow2" {
return stage
}
for _, pipelineIf := range pipelines {
pipeline := pipelineIf.(map[string]interface{})
if pipeline["name"].(string) == pipelineName {
return pipeline
}
return nil
}
qcowStage := findQcowStage()
require.NotNil(t, qcowStage)
return nil
}

func findStageFromOsbuildPipeline(t *testing.T, pipeline map[string]interface{}, stageName string) map[string]interface{} {

stages := pipeline["stages"].([]interface{})
for _, stageIf := range stages {
stage := stageIf.(map[string]interface{})
if stage["type"].(string) == stageName {
return stage
}
}
return nil
}

func TestBootcDiskImageInstantiateNoBuildpipelineForQcow2(t *testing.T) {
osbuildManifest := makeBootcDiskImageOsbuildManifest(t, nil)

qcowPipeline := findPipelineFromOsbuildManifest(t, osbuildManifest, "qcow2")
require.NotNil(t, qcowPipeline)
// no build pipeline for qcow2
assert.Equal(t, qcowStage["build"], nil)
assert.Equal(t, qcowPipeline["build"], nil)
}

func TestBootcDiskImageUsesBootupd(t *testing.T) {
osbuildManifest := makeBootcDiskImageOsbuildManifest(t, nil)

// check that bootupd 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})

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"])
}
}
}
13 changes: 11 additions & 2 deletions pkg/image/ostree_disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,15 @@ func NewOSTreeDiskImageFromContainer(container container.SourceSpec, ref string)
}
}

func baseRawOstreeImage(img *OSTreeDiskImage, buildPipeline manifest.Build) *manifest.RawOSTreeImage {
type baseRawOstreeImageOpts struct {
useBootupd bool
}

func baseRawOstreeImage(img *OSTreeDiskImage, buildPipeline manifest.Build, opts *baseRawOstreeImageOpts) *manifest.RawOSTreeImage {
if opts == nil {
opts = &baseRawOstreeImageOpts{}
}

var osPipeline *manifest.OSTreeDeployment
switch {
case img.CommitSource != nil:
Expand All @@ -98,6 +106,7 @@ func baseRawOstreeImage(img *OSTreeDiskImage, buildPipeline manifest.Build) *man
osPipeline.FIPS = img.FIPS
osPipeline.IgnitionPlatform = img.IgnitionPlatform
osPipeline.LockRoot = img.LockRoot
osPipeline.UseBootupd = opts.useBootupd

// other image types (e.g. live) pass the workload to the pipeline.
if img.Workload != nil {
Expand Down Expand Up @@ -127,7 +136,7 @@ func (img *OSTreeDiskImage) InstantiateManifest(m *manifest.Manifest,
panic(fmt.Sprintf("no compression is allowed with %q format for %q", imgFormat, img.name))
}

baseImage := baseRawOstreeImage(img, buildPipeline)
baseImage := baseRawOstreeImage(img, buildPipeline, nil)
switch img.Platform.GetImageFormat() {
case platform.FORMAT_VMDK:
vmdkPipeline := manifest.NewVMDK(buildPipeline, baseImage)
Expand Down
2 changes: 1 addition & 1 deletion pkg/image/ostree_simplified_installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (img *OSTreeSimplifiedInstaller) InstantiateManifest(m *manifest.Manifest,
imageFilename := "image.raw.xz"

// image in simplified installer is always compressed
compressedImage := manifest.NewXZ(buildPipeline, baseRawOstreeImage(img.rawImage, buildPipeline))
compressedImage := manifest.NewXZ(buildPipeline, baseRawOstreeImage(img.rawImage, buildPipeline, nil))
compressedImage.SetFilename(imageFilename)

coiPipeline := manifest.NewCoreOSInstaller(
Expand Down
37 changes: 21 additions & 16 deletions pkg/manifest/ostree_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ type OSTreeDeployment struct {
// Lock the root account in the deployment unless the user defined root
// user options in the build configuration.
LockRoot bool

// Use bootupd instead of grub2 as the bootloader
UseBootupd bool
}

// NewOSTreeCommitDeployment creates a pipeline for an ostree deployment from a
Expand Down Expand Up @@ -411,22 +414,24 @@ func (p *OSTreeDeployment) serialize() osbuild.Pipeline {
}
}

grubOptions := osbuild.NewGrub2StageOptions(p.PartitionTable,
strings.Join(kernelOpts, " "),
"",
p.platform.GetUEFIVendor() != "",
p.platform.GetBIOSPlatform(),
p.platform.GetUEFIVendor(), true)
grubOptions.Greenboot = true
grubOptions.Ignition = p.IgnitionPlatform != ""
grubOptions.Config = &osbuild.GRUB2Config{
Default: "saved",
Timeout: 1,
TerminalOutput: []string{"console"},
}
bootloader := osbuild.NewGRUB2Stage(grubOptions)
bootloader.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(bootloader)
if !p.UseBootupd {
grubOptions := osbuild.NewGrub2StageOptions(p.PartitionTable,
strings.Join(kernelOpts, " "),
"",
p.platform.GetUEFIVendor() != "",
p.platform.GetBIOSPlatform(),
p.platform.GetUEFIVendor(), true)
grubOptions.Greenboot = true
grubOptions.Ignition = p.IgnitionPlatform != ""
grubOptions.Config = &osbuild.GRUB2Config{
Default: "saved",
Timeout: 1,
TerminalOutput: []string{"console"},
}
bootloader := osbuild.NewGRUB2Stage(grubOptions)
bootloader.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(bootloader)
}

// First create custom directories, because some of the files may depend on them
if len(p.Directories) > 0 {
Expand Down
38 changes: 36 additions & 2 deletions pkg/manifest/raw_ostree.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,47 @@ func (p *RawOSTreeImage) serialize() osbuild.Pipeline {
pipeline.AddStage(stage)
}

if grubLegacy := p.treePipeline.platform.GetBIOSPlatform(); grubLegacy != "" {
pipeline.AddStage(osbuild.NewGrub2InstStage(osbuild.NewGrub2InstStageOption(p.Filename(), pt, grubLegacy)))
if p.treePipeline.UseBootupd {
p.addBootupdStage(&pipeline)
} else {
p.maybeAddGrubInstStage(&pipeline)
}

return pipeline
}

func (p *RawOSTreeImage) addBootupdStage(pipeline *osbuild.Pipeline) {
pt := p.treePipeline.PartitionTable

treeBootupdDevices, treeBootupdMounts := osbuild.GenBootupdDevicesMounts(p.Filename(), pt)
opts := &osbuild.BootupdStageOptions{
Deployment: &osbuild.OSTreeDeployment{
OSName: p.treePipeline.osName,
Ref: p.treePipeline.ref,
},
StaticConfigs: true,
}
if legacyBios := p.treePipeline.platform.GetBIOSPlatform(); legacyBios != "" {
opts.Bios = &osbuild.BootupdStageOptionsBios{
Device: "disk",
}
}
bootupd, err := osbuild.NewBootupdStage(opts, treeBootupdDevices, treeBootupdMounts)
if err != nil {
panic(err)
}

pipeline.AddStage(bootupd)
}

func (p *RawOSTreeImage) maybeAddGrubInstStage(pipeline *osbuild.Pipeline) {
pt := p.treePipeline.PartitionTable

if grubLegacy := p.treePipeline.platform.GetBIOSPlatform(); grubLegacy != "" {
pipeline.AddStage(osbuild.NewGrub2InstStage(osbuild.NewGrub2InstStageOption(p.Filename(), pt, grubLegacy)))
}
}

func (p *RawOSTreeImage) Export() *artifact.Artifact {
p.Base.export = true
return artifact.New(p.Name(), p.Filename(), nil)
Expand Down
18 changes: 18 additions & 0 deletions pkg/osbuild/bootupd_stage.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package osbuild
import (
"fmt"
"sort"

"github.com/osbuild/images/pkg/disk"
)

type BootupdStageOptionsBios struct {
Expand Down Expand Up @@ -73,3 +75,19 @@ func NewBootupdStage(opts *BootupdStageOptions, devices *Devices, mounts []Mount
Mounts: mounts,
}, nil
}

func GenBootupdDevicesMounts(filename string, pt *disk.PartitionTable) (*Devices, []Mount) {
_, mounts, devices, err := genMountsDevicesFromPt(filename, pt)
if err != nil {
panic(err)
}
devices["disk"] = Device{
Type: "org.osbuild.loopback",
Options: &LoopbackDeviceOptions{
Filename: filename,
},
}
stageDevices := Devices(devices)

return &stageDevices, mounts
}
Loading

0 comments on commit 0a33801

Please sign in to comment.