Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

osbuild: add new bootupd stage (HMS-3318) #374

Merged
merged 1 commit into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
}
]
}`)
}
Loading