Skip to content

Commit

Permalink
osbuild: add new bootupd stage
Browse files Browse the repository at this point in the history
Add support for the new `org.osbuild.bootupd` stage that got added
in osbuild/osbuild#1519
  • Loading branch information
mvo5 committed Jan 16, 2024
1 parent 65d5643 commit 41292c8
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 0 deletions.
75 changes: 75 additions & 0 deletions pkg/osbuild/bootupd_stage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package osbuild

import (
"fmt"
"sort"
)

type BootupdStageOptionsBios struct {
Device string `json:"device"`
Partition int `json:"partition,omitempty"`
}

type BootupdStageOptions struct {
Deployment *OSTreeDeployment `json:"deployment,omitempty"`
StaticConfigs bool `json:"static-configs"`
Bios *BootupdStageOptionsBios `json:"bios,omitempty"`
}

func (BootupdStageOptions) isStageOptions() {}

func (opts *BootupdStageOptions) validate(devices map[string]Device) error {
if opts.Bios != nil && opts.Bios.Device != "" {
if _, ok := devices[opts.Bios.Device]; !ok {
var devnames []string
for devname := range devices {
devnames = append(devnames, devname)
}
sort.Strings(devnames)
return fmt.Errorf("cannot find expected device %q for bootupd bios option in %v", opts.Bios.Device, devnames)
}
}
return nil
}

// validateBootupdMounts ensures that all required mounts for the bootup
// stage are generated. Right now the stage requires root, boot and boot/efi
// to find all the bootloader configs
func validateBootupdMounts(mounts []Mount) error {
requiredMounts := map[string]bool{
"/": true,
"/boot": true,
"/boot/efi": true,
}
for _, mnt := range mounts {
delete(requiredMounts, mnt.Target)
}
if len(requiredMounts) != 0 {
var missingMounts []string
for mnt := range requiredMounts {
missingMounts = append(missingMounts, mnt)
}
sort.Strings(missingMounts)
return fmt.Errorf("required mounts for bootupd stage %v missing", missingMounts)
}
return nil
}

// NewBootupdStage creates a new stage for the org.osbuild.bootupd stage. It
// requires a mount setup of "/", "/boot" and "/boot/efi" right now so that
// bootupd can find and install all required bootloader bits.
func NewBootupdStage(opts *BootupdStageOptions, devices *Devices, mounts *Mounts) (*Stage, error) {
if err := validateBootupdMounts(*mounts); err != nil {
return nil, err
}
if err := opts.validate(*devices); err != nil {
return nil, err
}

return &Stage{
Type: "org.osbuild.bootupd",
Options: opts,
Devices: *devices,
Mounts: *mounts,
}, nil
}
145 changes: 145 additions & 0 deletions pkg/osbuild/bootupd_stage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package osbuild_test

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

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

func makeOsbuildMounts(targets ...string) osbuild.Mounts {
var mnts []osbuild.Mount
for _, target := range targets {
mnts = append(mnts, osbuild.Mount{
Type: "org.osbuild.ext4",
Name: "mnt-" + target,
Source: "dev-" + target,
Target: target,
})
}
return mnts
}

func makeOsbuildDevices(devnames ...string) osbuild.Devices {
devices := make(map[string]osbuild.Device)
for _, devname := range devnames {
devices[devname] = osbuild.Device{
Type: "orgosbuild.loopback",
}
}
return devices
}

func TestBootupdStageNewHappy(t *testing.T) {
opts := &osbuild.BootupdStageOptions{
StaticConfigs: true,
}
devices := makeOsbuildDevices("dev-/", "dev-/boot", "dev-/boot/efi")
mounts := makeOsbuildMounts("/", "/boot", "/boot/efi")

expectedStage := &osbuild.Stage{
Type: "org.osbuild.bootupd",
Options: opts,
Devices: devices,
Mounts: mounts,
}
stage, err := osbuild.NewBootupdStage(opts, &devices, &mounts)
require.Nil(t, err)
assert.Equal(t, stage, expectedStage)
}

func TestBootupdStageMissingMounts(t *testing.T) {
opts := &osbuild.BootupdStageOptions{
StaticConfigs: true,
}
devices := makeOsbuildDevices("dev-/")
mounts := makeOsbuildMounts("/")

stage, err := osbuild.NewBootupdStage(opts, &devices, &mounts)
assert.ErrorContains(t, err, "required mounts for bootupd stage [/boot /boot/efi] missing")
require.Nil(t, stage)
}

func TestBootupdStageMissingDevice(t *testing.T) {
opts := &osbuild.BootupdStageOptions{
Bios: &osbuild.BootupdStageOptionsBios{
Device: "disk",
},
}
devices := makeOsbuildDevices("dev-/", "dev-/boot", "dev-/boot/efi")
mounts := makeOsbuildMounts("/", "/boot", "/boot/efi")

stage, err := osbuild.NewBootupdStage(opts, &devices, &mounts)
assert.ErrorContains(t, err, `cannot find expected device "disk" for bootupd bios option in [dev-/ dev-/boot dev-/boot/efi]`)
require.Nil(t, stage)
}

func TestBootupdStageJsonHappy(t *testing.T) {
opts := &osbuild.BootupdStageOptions{
Deployment: &osbuild.OSTreeDeployment{
OSName: "default",
Ref: "ostree/1/1/0",
},
StaticConfigs: true,
Bios: &osbuild.BootupdStageOptionsBios{
Device: "disk",
},
}
devices := makeOsbuildDevices("disk", "dev-/", "dev-/boot", "dev-/boot/efi")
mounts := makeOsbuildMounts("/", "/boot", "/boot/efi")

stage, err := osbuild.NewBootupdStage(opts, &devices, &mounts)
require.Nil(t, err)
stageJson, err := json.MarshalIndent(stage, "", " ")
require.Nil(t, err)
assert.Equal(t, string(stageJson), `{
"type": "org.osbuild.bootupd",
"options": {
"deployment": {
"osname": "default",
"ref": "ostree/1/1/0"
},
"static-configs": true,
"bios": {
"device": "disk"
}
},
"devices": {
"dev-/": {
"type": "orgosbuild.loopback"
},
"dev-/boot": {
"type": "orgosbuild.loopback"
},
"dev-/boot/efi": {
"type": "orgosbuild.loopback"
},
"disk": {
"type": "orgosbuild.loopback"
}
},
"mounts": [
{
"name": "mnt-/",
"type": "org.osbuild.ext4",
"source": "dev-/",
"target": "/"
},
{
"name": "mnt-/boot",
"type": "org.osbuild.ext4",
"source": "dev-/boot",
"target": "/boot"
},
{
"name": "mnt-/boot/efi",
"type": "org.osbuild.ext4",
"source": "dev-/boot/efi",
"target": "/boot/efi"
}
]
}`)
}

0 comments on commit 41292c8

Please sign in to comment.