osbuild2: add new Grub2LegacyStage for org.osbuild.grub2.legacy
Expose osbuild's `org.osbuild.grub2.legacy` stage.
This commit is contained in:
parent
75d7bbaaa3
commit
5082c36ca5
2 changed files with 261 additions and 0 deletions
227
internal/osbuild2/grub2_legacy_stage.go
Normal file
227
internal/osbuild2/grub2_legacy_stage.go
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
package osbuild2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/common"
|
||||
"github.com/osbuild/osbuild-composer/internal/disk"
|
||||
)
|
||||
|
||||
type GRUB2FSDesc struct {
|
||||
Device string `json:"device,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
UUID *uuid.UUID `json:"uuid,omitempty"`
|
||||
}
|
||||
|
||||
func (d GRUB2FSDesc) validate() error {
|
||||
|
||||
have := make([]string, 0, 3)
|
||||
if d.Device != "" {
|
||||
have = append(have, "`device`")
|
||||
}
|
||||
|
||||
if d.Label != "" {
|
||||
have = append(have, "`label`")
|
||||
}
|
||||
|
||||
if d.UUID != nil {
|
||||
have = append(have, "`uuid`")
|
||||
}
|
||||
|
||||
count := len(have)
|
||||
if count == 0 {
|
||||
return fmt.Errorf("need `device`, `label`, or `uuid`")
|
||||
} else if count > 1 {
|
||||
return fmt.Errorf("must only specify one of %s", strings.Join(have, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type GRUB2Product struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Nick string `json:"nick,omitempty"`
|
||||
}
|
||||
|
||||
func (p GRUB2Product) validate() error {
|
||||
if p.Name == "" {
|
||||
return fmt.Errorf("need `Name`")
|
||||
}
|
||||
if p.Version == "" {
|
||||
return fmt.Errorf("need `Version`")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GRUB2MenuEntry struct {
|
||||
Default *bool `json:"default,omitempty"`
|
||||
Id string `json:"id,omitempty"`
|
||||
Kernel string `json:"kernel,omitempty"`
|
||||
Product GRUB2Product `json:"product,omitempty"`
|
||||
}
|
||||
|
||||
func (e GRUB2MenuEntry) validate() (err error) {
|
||||
if e.Id == "" {
|
||||
return fmt.Errorf("need `Id`")
|
||||
}
|
||||
if e.Kernel == "" {
|
||||
return fmt.Errorf("need `Kernel`")
|
||||
}
|
||||
if err = e.Product.validate(); err != nil {
|
||||
return fmt.Errorf("`Product` error: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GRUB2BIOS struct {
|
||||
Platform string `json:"platform,"`
|
||||
}
|
||||
|
||||
type GRUB2LegacyConfig struct {
|
||||
GRUB2Config
|
||||
CmdLine string `json:"cmdline,omitempty"`
|
||||
Distributor string `json:"distributor,omitempty"`
|
||||
}
|
||||
|
||||
type GRUB2LegacyStageOptions struct {
|
||||
// Required
|
||||
RootFS GRUB2FSDesc `json:"rootfs"`
|
||||
Entries []GRUB2MenuEntry `json:"entries"`
|
||||
|
||||
// One of
|
||||
BIOS *GRUB2BIOS `json:"bios,omitempty"`
|
||||
UEFI *GRUB2UEFI `json:"uefi,omitempty"`
|
||||
|
||||
// Optional
|
||||
BootFS *GRUB2FSDesc `json:"bootfs,omitempty"`
|
||||
WriteDefaults *bool `json:"write_defaults,omitempty"`
|
||||
Config *GRUB2LegacyConfig `json:"config,omitempty"`
|
||||
}
|
||||
|
||||
func (GRUB2LegacyStageOptions) isStageOptions() {}
|
||||
|
||||
func MakeGrub2MenuEntries(id string, kernelVer string, product GRUB2Product, rescue bool) []GRUB2MenuEntry {
|
||||
entries := []GRUB2MenuEntry{
|
||||
{
|
||||
Default: common.BoolToPtr(true),
|
||||
Id: id,
|
||||
Product: product,
|
||||
Kernel: kernelVer,
|
||||
},
|
||||
}
|
||||
|
||||
if rescue {
|
||||
entry := GRUB2MenuEntry{
|
||||
Id: id,
|
||||
Product: product,
|
||||
Kernel: "0-rescue-ffffffffffffffffffffffffffffffff",
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
func NewGrub2LegacyStageOptions(cfg *GRUB2Config,
|
||||
pt *disk.PartitionTable,
|
||||
kernelOptions []string,
|
||||
legacy string,
|
||||
uefi string,
|
||||
entries []GRUB2MenuEntry) *GRUB2LegacyStageOptions {
|
||||
|
||||
rootFs := pt.FindMountable("/")
|
||||
if rootFs == nil {
|
||||
panic("root filesystem must be defined for grub2 stage, this is a programming error")
|
||||
}
|
||||
|
||||
kopts := strings.Join(kernelOptions, " ")
|
||||
|
||||
rootFsUUID := uuid.MustParse(rootFs.GetFSSpec().UUID)
|
||||
stageOptions := GRUB2LegacyStageOptions{
|
||||
RootFS: GRUB2FSDesc{UUID: &rootFsUUID},
|
||||
Entries: entries,
|
||||
Config: &GRUB2LegacyConfig{
|
||||
CmdLine: kopts,
|
||||
Distributor: "$(sed 's, release .*$,,g' /etc/system-release)",
|
||||
},
|
||||
}
|
||||
|
||||
if cfg != nil {
|
||||
stageOptions.Config.GRUB2Config = *cfg
|
||||
}
|
||||
|
||||
bootFs := pt.FindMountable("/boot")
|
||||
if bootFs != nil {
|
||||
bootFsUUID := uuid.MustParse(bootFs.GetFSSpec().UUID)
|
||||
stageOptions.BootFS = &GRUB2FSDesc{UUID: &bootFsUUID}
|
||||
}
|
||||
|
||||
if legacy != "" {
|
||||
stageOptions.BIOS = &GRUB2BIOS{
|
||||
Platform: legacy,
|
||||
}
|
||||
}
|
||||
|
||||
if uefi != "" {
|
||||
stageOptions.UEFI = &GRUB2UEFI{
|
||||
Vendor: uefi,
|
||||
}
|
||||
}
|
||||
|
||||
return &stageOptions
|
||||
}
|
||||
|
||||
func (o GRUB2LegacyStageOptions) validate() error {
|
||||
// Check we have the required options
|
||||
err := o.RootFS.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("`rootfs` error: %w", err)
|
||||
}
|
||||
|
||||
if o.BIOS == nil && o.UEFI == nil {
|
||||
return fmt.Errorf("need `BIOS` or `UEFI`")
|
||||
}
|
||||
|
||||
if o.BIOS != nil && o.BIOS.Platform == "" {
|
||||
return fmt.Errorf("need `BIOS.Platform`")
|
||||
}
|
||||
|
||||
if o.UEFI != nil && o.UEFI.Vendor == "" {
|
||||
return fmt.Errorf("need `UEFI.Vendor`")
|
||||
}
|
||||
|
||||
if len(o.Entries) == 0 {
|
||||
return fmt.Errorf("at least one entry is required")
|
||||
}
|
||||
|
||||
for i, entry := range o.Entries {
|
||||
if err = entry.validate(); err != nil {
|
||||
return fmt.Errorf("menu entry %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// check optional arguments
|
||||
if o.BootFS != nil {
|
||||
err = o.BootFS.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("`bootfs` error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewGrub2LegacyStage(options *GRUB2LegacyStageOptions) *Stage {
|
||||
if err := options.validate(); err != nil {
|
||||
panic(fmt.Errorf("grub2.legacy validation failed: %w", err))
|
||||
}
|
||||
|
||||
return &Stage{
|
||||
Type: "org.osbuild.grub2.legacy",
|
||||
Options: options,
|
||||
}
|
||||
}
|
||||
34
internal/osbuild2/grub2_legacy_stage_test.go
Normal file
34
internal/osbuild2/grub2_legacy_stage_test.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package osbuild2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGrub2LegacyStage_Validation(t *testing.T) {
|
||||
|
||||
options := GRUB2LegacyStageOptions{}
|
||||
|
||||
err := options.validate()
|
||||
assert.Error(t, err)
|
||||
|
||||
options.RootFS.Device = "/dev/sda"
|
||||
err = options.validate()
|
||||
assert.Error(t, err)
|
||||
|
||||
prod := GRUB2Product{
|
||||
Name: "Fedora",
|
||||
Nick: "Foo",
|
||||
Version: "1",
|
||||
}
|
||||
options.Entries = MakeGrub2MenuEntries("id", "kernel", prod, false)
|
||||
err = options.validate()
|
||||
assert.Error(t, err)
|
||||
|
||||
options.BIOS = &GRUB2BIOS{
|
||||
Platform: "i386-pc",
|
||||
}
|
||||
err = options.validate()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue