Skip to content

Commit

Permalink
fix: imager should support different Talos versions
Browse files Browse the repository at this point in the history
Add some quirks to make images generated with newer Talos compatible
with images generated by older Talos.

Specifically, reset options were adding in Talos 1.4, so we shouldn't
add them for older versions.

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
  • Loading branch information
smira committed Dec 22, 2023
1 parent d6342cd commit 0a30ef7
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 19 deletions.
2 changes: 1 addition & 1 deletion cmd/installer/pkg/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func NewInstaller(ctx context.Context, cmdline *procfs.Cmdline, mode Mode, opts
if !bootLoaderPresent {
if mode.IsImage() {
// on image creation, use the bootloader based on options
i.bootloader = bootloader.New(opts.ImageSecureboot)
i.bootloader = bootloader.New(opts.ImageSecureboot, opts.Version)
} else {
// on install/upgrade perform automatic detection
i.bootloader = bootloader.NewAuto()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/sdboot"
"github.com/siderolabs/talos/pkg/imager/quirks"
)

// Bootloader describes a bootloader.
Expand Down Expand Up @@ -62,10 +63,13 @@ func NewAuto() Bootloader {
}

// New returns a new bootloader based on the secureboot flag.
func New(secureboot bool) Bootloader {
func New(secureboot bool, talosVersion string) Bootloader {
if secureboot {
return sdboot.New()
}

return grub.NewConfig()
g := grub.NewConfig()
g.AddResetOption = quirks.New(talosVersion).SupportsResetGRUBOption()

return g
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,44 +69,48 @@ func Decode(c []byte) (*Config, error) {
return nil, err
}

entries, err := parseEntries(c)
entries, hasResetOption, err := parseEntries(c)
if err != nil {
return nil, err
}

conf := Config{
Default: defaultEntry,
Fallback: fallbackEntry,
Entries: entries,
Default: defaultEntry,
Fallback: fallbackEntry,
Entries: entries,
AddResetOption: hasResetOption,
}

return &conf, nil
}

func parseEntries(conf []byte) (map[BootLabel]MenuEntry, error) {
func parseEntries(conf []byte) (map[BootLabel]MenuEntry, bool, error) {
entries := make(map[BootLabel]MenuEntry)
hasResetOption := false

matches := menuEntryRegex.FindAllSubmatch(conf, -1)
for _, m := range matches {
if len(m) != 3 {
return nil, fmt.Errorf("conf block: expected 3 matches, got %d", len(m))
return nil, false, fmt.Errorf("conf block: expected 3 matches, got %d", len(m))
}

confBlock := m[2]

linux, cmdline, initrd, err := parseConfBlock(confBlock)
if err != nil {
return nil, err
return nil, false, err
}

name := string(m[1])

bootEntry, err := ParseBootLabel(name)
if err != nil {
return nil, err
return nil, false, err
}

if bootEntry == BootReset {
hasResetOption = true

continue
}

Expand All @@ -118,7 +122,7 @@ func parseEntries(conf []byte) (map[BootLabel]MenuEntry, error) {
}
}

return entries, nil
return entries, hasResetOption, nil
}

func parseConfBlock(block []byte) (linux, cmdline, initrd string, err error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ menuentry "{{ $entry.Name }}" {
}
{{ end -}}
{{ if .AddResetOption -}}
{{ $defaultEntry := index .Entries .Default -}}
menuentry "Reset Talos installation and return to maintenance mode" {
set gfxmode=auto
set gfxpayload=text
linux {{ $defaultEntry.Linux }} {{ quote $defaultEntry.Cmdline }} talos.experimental.wipe=system:EPHEMERAL,STATE
initrd {{ $defaultEntry.Initrd }}
}
{{ end -}}
`

// Write the grub configuration to the given file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import (

// Config represents a grub configuration file (grub.cfg).
type Config struct {
Default BootLabel
Fallback BootLabel
Entries map[BootLabel]MenuEntry
Default BootLabel
Fallback BootLabel
Entries map[BootLabel]MenuEntry
AddResetOption bool
}

// MenuEntry represents a grub menu entry in the grub config file.
Expand All @@ -35,8 +36,9 @@ func (e bootloaderNotInstalledError) Error() string {
// NewConfig creates a new grub configuration (nothing is written to disk).
func NewConfig() *Config {
return &Config{
Default: BootA,
Entries: map[BootLabel]MenuEntry{},
Default: BootA,
Entries: map[BootLabel]MenuEntry{},
AddResetOption: true,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ var (

//go:embed testdata/grub_write_test.cfg
newConfig string

//go:embed testdata/grub_write_no_reset_test.cfg
newNoResetConfig string
)

func TestDecode(t *testing.T) {
Expand All @@ -50,6 +53,8 @@ func TestDecode(t *testing.T) {
assert.Equal(t, "cmdline B", b.Cmdline)
assert.True(t, strings.HasPrefix(b.Linux, "/B/"))
assert.True(t, strings.HasPrefix(b.Initrd, "/B/"))

assert.True(t, conf.AddResetOption)
}

func TestEncodeDecode(t *testing.T) {
Expand Down Expand Up @@ -110,6 +115,31 @@ func TestWrite(t *testing.T) {
assert.Equal(t, newConfig, string(written))
}

//nolint:errcheck
func TestWriteNoReset(t *testing.T) {
oldName := version.Name

t.Cleanup(func() {
version.Name = oldName
})

version.Name = "TestOld"

tempFile, _ := os.CreateTemp("", "talos-test-grub-*.cfg")

t.Cleanup(func() { require.NoError(t, os.Remove(tempFile.Name())) })

config := grub.NewConfig()
config.AddResetOption = false
require.NoError(t, config.Put(grub.BootA, "cmdline A", "v0.0.1"))

err := config.Write(tempFile.Name(), t.Logf)
assert.NoError(t, err)

written, _ := os.ReadFile(tempFile.Name())
assert.Equal(t, newNoResetConfig, string(written))
}

func TestPut(t *testing.T) {
config := grub.NewConfig()
require.NoError(t, config.Put(grub.BootA, "cmdline A", "v1.2.3"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
set default="A - TestOld v0.0.1"

set timeout=3

insmod all_video

terminal_input console
terminal_output console

menuentry "A - TestOld v0.0.1" {
set gfxmode=auto
set gfxpayload=text
linux /A/vmlinuz cmdline A
initrd /A/initramfs.xz
}
2 changes: 2 additions & 0 deletions pkg/imager/iso/grub.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ menuentry "Talos ISO" {
initrd /boot/initramfs.xz
}

{{ if .AddResetOption -}}
menuentry "Reset Talos installation" {
set gfxmode=auto
set gfxpayload=text
linux /boot/vmlinuz {{ quote .Cmdline }} talos.experimental.wipe=system
initrd /boot/initramfs.xz
}
{{ end -}}
8 changes: 6 additions & 2 deletions pkg/imager/iso/grub.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/siderolabs/go-cmd/pkg/cmd"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
"github.com/siderolabs/talos/pkg/imager/quirks"
"github.com/siderolabs/talos/pkg/imager/utils"
)

Expand All @@ -24,6 +25,7 @@ type GRUBOptions struct {
KernelPath string
InitramfsPath string
Cmdline string
Version string

ScratchDir string

Expand Down Expand Up @@ -59,9 +61,11 @@ func CreateGRUB(printf func(string, ...any), options GRUBOptions) error {
}

if err = tmpl.Execute(&grubCfg, struct {
Cmdline string
Cmdline string
AddResetOption bool
}{
Cmdline: options.Cmdline,
Cmdline: options.Cmdline,
AddResetOption: quirks.New(options.Version).SupportsResetGRUBOption(),
}); err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/imager/out.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Repor
KernelPath: i.prof.Input.Kernel.Path,
InitramfsPath: i.initramfsPath,
Cmdline: i.cmdline,
Version: i.prof.Version,

ScratchDir: scratchSpace,
OutPath: path,
Expand Down
35 changes: 35 additions & 0 deletions pkg/imager/quirks/quirks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package quirks contains the quirks for Talos image generation.
package quirks

import "github.com/blang/semver/v4"

// Quirks contains the quirks for Talos image generation.
type Quirks struct {
v *semver.Version
}

// New returns a new Quirks instance based on Talos version for the image.
func New(talosVersion string) Quirks {
v, err := semver.ParseTolerant(talosVersion) // ignore the error
if err != nil {
return Quirks{}
}

return Quirks{v: &v}
}

var minVersionResetOption = semver.MustParse("1.4.0")

// SupportsResetGRUBOption returns true if the Talos version supports the reset option in GRUB menu (image and ISO).
func (q Quirks) SupportsResetGRUBOption() bool {
// if the version doesn't parse, we assume it's latest Talos
if q.v == nil {
return true
}

return q.v.GTE(minVersionResetOption)
}
37 changes: 37 additions & 0 deletions pkg/imager/quirks/quirks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package quirks_test

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/siderolabs/talos/pkg/imager/quirks"
)

func TestSupportsResetOption(t *testing.T) {
for _, test := range []struct {
version string

expected bool
}{
{
version: "1.5.0",
expected: true,
},
{
expected: true,
},
{
version: "1.3.7",
expected: false,
},
} {
t.Run(test.version, func(t *testing.T) {
assert.Equal(t, test.expected, quirks.New(test.version).SupportsResetGRUBOption())
})
}
}

0 comments on commit 0a30ef7

Please sign in to comment.