mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-30 16:01:12 +01:00
fix: imager should support different Talos versions
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>
This commit is contained in:
parent
d6342cda53
commit
0a30ef7845
@ -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()
|
||||
|
||||
@ -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.
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -32,6 +32,7 @@ menuentry "{{ $entry.Name }}" {
|
||||
}
|
||||
{{ end -}}
|
||||
|
||||
{{ if .AddResetOption -}}
|
||||
{{ $defaultEntry := index .Entries .Default -}}
|
||||
menuentry "Reset Talos installation and return to maintenance mode" {
|
||||
set gfxmode=auto
|
||||
@ -39,6 +40,7 @@ menuentry "Reset Talos installation and return to maintenance mode" {
|
||||
linux {{ $defaultEntry.Linux }} {{ quote $defaultEntry.Cmdline }} talos.experimental.wipe=system:EPHEMERAL,STATE
|
||||
initrd {{ $defaultEntry.Initrd }}
|
||||
}
|
||||
{{ end -}}
|
||||
`
|
||||
|
||||
// Write the grub configuration to the given file.
|
||||
|
||||
@ -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.
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
@ -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) {
|
||||
@ -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"))
|
||||
|
||||
15
internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/testdata/grub_write_no_reset_test.cfg
vendored
Normal file
15
internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/testdata/grub_write_no_reset_test.cfg
vendored
Normal file
@ -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
|
||||
}
|
||||
@ -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 -}}
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -24,6 +25,7 @@ type GRUBOptions struct {
|
||||
KernelPath string
|
||||
InitramfsPath string
|
||||
Cmdline string
|
||||
Version string
|
||||
|
||||
ScratchDir string
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
35
pkg/imager/quirks/quirks.go
Normal file
35
pkg/imager/quirks/quirks.go
Normal file
@ -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
pkg/imager/quirks/quirks_test.go
Normal file
37
pkg/imager/quirks/quirks_test.go
Normal file
@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user