Skip to content

Commit

Permalink
feat: implement Install for imager overlays
Browse files Browse the repository at this point in the history
Implement `Install` for imager overlays.
Also add support for generating installers.

Depends on: siderolabs#8377

Fixes: siderolabs#8350
Fixes: siderolabs#8351
Fixes: siderolabs#8350

Signed-off-by: Noel Georgi <git@frezbo.dev>
  • Loading branch information
frezbo committed Mar 1, 2024
1 parent 8125e75 commit b00f301
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 57 deletions.
23 changes: 23 additions & 0 deletions cmd/installer/cmd/imager/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"os"
"runtime"
"strings"

"github.com/dustin/go-humanize"
"github.com/siderolabs/gen/xslices"
Expand All @@ -22,6 +23,7 @@ import (
"github.com/siderolabs/talos/pkg/imager"
"github.com/siderolabs/talos/pkg/imager/profile"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/overlay"
"github.com/siderolabs/talos/pkg/reporter"
)

Expand All @@ -39,6 +41,7 @@ var cmdFlags struct {
TarToStdout bool
OverlayName string
OverlayImage string
OverlayOptions []string
}

// rootCmd represents the base command when called without any subcommands.
Expand Down Expand Up @@ -76,12 +79,30 @@ var rootCmd = &cobra.Command{
},
}

extraOverlayOptions := overlay.ExtraOptions{}

for _, option := range cmdFlags.OverlayOptions {
k, v, _ := strings.Cut(option, "=")

if strings.HasPrefix(v, "@") {
data, err := os.ReadFile(v[1:])
if err != nil {
return err
}

v = string(data)
}

extraOverlayOptions[k] = v
}

if cmdFlags.OverlayName != "" || cmdFlags.OverlayImage != "" {
prof.Overlay = &profile.OverlayOptions{
Name: cmdFlags.OverlayName,
Image: profile.ContainerAsset{
ImageRef: cmdFlags.OverlayImage,
},
ExtraOptions: extraOverlayOptions,
}
}

Expand Down Expand Up @@ -176,6 +197,8 @@ func init() {
rootCmd.PersistentFlags().BoolVar(&cmdFlags.TarToStdout, "tar-to-stdout", false, "Tar output and send to stdout")
rootCmd.PersistentFlags().StringVar(&cmdFlags.OverlayName, "overlay-name", "", "The name of the overlay to use")
rootCmd.PersistentFlags().StringVar(&cmdFlags.OverlayImage, "overlay-image", "", "The image reference to the overlay")
rootCmd.PersistentFlags().StringArrayVar(&cmdFlags.OverlayOptions, "overlay-option", []string{}, "Extra options to pass to the overlay")
rootCmd.MarkFlagsMutuallyExclusive("board", "overlay-name")
rootCmd.MarkFlagsMutuallyExclusive("board", "overlay-image")
rootCmd.MarkFlagsMutuallyExclusive("board", "overlay-option")
}
57 changes: 37 additions & 20 deletions cmd/installer/pkg/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,25 @@ import (
"fmt"
"log"
"os"
"path/filepath"
"syscall"
"time"

"github.com/siderolabs/go-blockdevice/blockdevice"
"github.com/siderolabs/go-procfs/procfs"
"github.com/siderolabs/go-retry/retry"
"gopkg.in/yaml.v3"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/board"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader"
bootloaderoptions "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options"
"github.com/siderolabs/talos/internal/pkg/meta"
"github.com/siderolabs/talos/internal/pkg/mount"
"github.com/siderolabs/talos/pkg/imager/overlay/executor"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/kernel"
"github.com/siderolabs/talos/pkg/machinery/overlay"
"github.com/siderolabs/talos/pkg/version"
)

Expand All @@ -41,6 +45,9 @@ type Options struct {
Zero bool
LegacyBIOSSupport bool
MetaValues MetaValues
OverlayInstaller overlay.Installer[overlay.ExtraOptions]
ScratchDir string
ExtraOptions overlay.ExtraOptions

// Options specific for the image creation mode.
ImageSecureboot bool
Expand Down Expand Up @@ -68,7 +75,7 @@ func (m Mode) IsImage() bool {
}

// Install installs Talos.
func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options) (err error) {
func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options) error {
cmdline := procfs.NewCmdline("")
cmdline.Append(constants.KernelParamPlatform, p.Name())

Expand All @@ -79,35 +86,33 @@ func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options)
cmdline.SetAll(p.KernelArgs().Strings())

// first defaults, then extra kernel args to allow extra kernel args to override defaults
if err = cmdline.AppendAll(kernel.DefaultArgs); err != nil {
if err := cmdline.AppendAll(kernel.DefaultArgs); err != nil {
return err
}

if opts.Board != constants.BoardNone {
// board 'rpi_4' was removed in Talos 1.5 in favor of `rpi_generic`
if opts.Board == "rpi_4" {
opts.Board = constants.BoardRPiGeneric
}

var b runtime.Board
if err := cmdline.AppendAll(
opts.ExtraKernelArgs,
procfs.WithOverwriteArgs("console"),
procfs.WithOverwriteArgs(constants.KernelParamPlatform),
procfs.WithDeleteNegatedArgs(),
); err != nil {
return err
}

b, err = board.NewBoard(opts.Board)
if _, err := os.Stat(constants.ImagerOverlayInstallerDefaultPath); err == nil {
extraOptionsBytes, err := os.ReadFile(constants.ImagerOverlayExtraOptionsPath)
if err != nil {
return err
}

cmdline.Append(constants.KernelParamBoard, b.Name())
var extraOptions overlay.ExtraOptions

cmdline.SetAll(b.KernelArgs().Strings())
}
if err := yaml.Unmarshal(extraOptionsBytes, &extraOptions); err != nil {
return fmt.Errorf("failed to unmarshal extra options: %w", err)
}

if err = cmdline.AppendAll(
opts.ExtraKernelArgs,
procfs.WithOverwriteArgs("console"),
procfs.WithOverwriteArgs(constants.KernelParamPlatform),
procfs.WithDeleteNegatedArgs(),
); err != nil {
return err
opts.OverlayInstaller = executor.New(constants.ImagerOverlayInstallerDefaultPath)
opts.ExtraOptions = extraOptions
}

i, err := NewInstaller(ctx, cmdline, mode, opts)
Expand Down Expand Up @@ -280,6 +285,7 @@ func (i *Installer) Install(ctx context.Context, mode Mode) (err error) {
return err
}

// TODO: frezbo: check if supports overlay quirk
if i.options.Board != constants.BoardNone {
var b runtime.Board

Expand All @@ -302,6 +308,17 @@ func (i *Installer) Install(ctx context.Context, mode Mode) (err error) {
}
}

if i.options.OverlayInstaller != nil {
if err = i.options.OverlayInstaller.Install(overlay.InstallOptions[overlay.ExtraOptions]{
InstallDisk: i.options.Disk,
MountPrefix: i.options.MountPrefix,
ArtifactsPath: filepath.Join(i.options.ScratchDir, constants.ImagerOverlayArtifactsPath),
ExtraOptions: i.options.ExtraOptions,
}); err != nil {
return err
}
}

if mode == ModeUpgrade || len(i.options.MetaValues.values) > 0 {
var (
metaState *meta.Meta
Expand Down
22 changes: 10 additions & 12 deletions pkg/imager/imager.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform"
"github.com/siderolabs/talos/internal/pkg/secureboot/uki"
"github.com/siderolabs/talos/pkg/imager/extensions"
"github.com/siderolabs/talos/pkg/imager/internal/overlay/executor"
"github.com/siderolabs/talos/pkg/imager/overlay/executor"
"github.com/siderolabs/talos/pkg/imager/profile"
"github.com/siderolabs/talos/pkg/imager/quirks"
"github.com/siderolabs/talos/pkg/imager/utils"
Expand All @@ -38,7 +38,7 @@ import (
type Imager struct {
prof profile.Profile

overlayInstaller overlay.Installer
overlayInstaller overlay.Installer[overlay.ExtraOptions]

tempDir string

Expand Down Expand Up @@ -170,7 +170,7 @@ func (i *Imager) handleOverlay(ctx context.Context, report *reporter.Reporter) e
return nil
}

tempOverlayPath := filepath.Join(i.tempDir, "overlay")
tempOverlayPath := filepath.Join(i.tempDir, constants.ImagerOverlayBasePath)

if err := os.MkdirAll(tempOverlayPath, 0o755); err != nil {
return fmt.Errorf("failed to create overlay directory: %w", err)
Expand All @@ -180,19 +180,17 @@ func (i *Imager) handleOverlay(ctx context.Context, report *reporter.Reporter) e
return err
}

// find all *.yaml files in the tempOverlayPath/profiles/ directory
profileYAMLs, err := filepath.Glob(filepath.Join(tempOverlayPath, "profiles", "*.yaml"))
// find all *.yaml files in the overlay/profiles/ directory
profileYAMLs, err := filepath.Glob(filepath.Join(i.tempDir, constants.ImagerOverlayProfilesPath, "*.yaml"))
if err != nil {
return fmt.Errorf("failed to find profiles: %w", err)
}

installerName := i.prof.Overlay.Name

if installerName == "" {
installerName = "default"
if i.prof.Overlay.Name == "" {
i.prof.Overlay.Name = constants.ImagerOverlayInstallerDefault
}

i.overlayInstaller = executor.New(filepath.Join(tempOverlayPath, "installers", installerName))
i.overlayInstaller = executor.New(filepath.Join(i.tempDir, constants.ImagerOverlayInstallersPath, i.prof.Overlay.Name))

for _, profilePath := range profileYAMLs {
profileName := strings.TrimSuffix(filepath.Base(profilePath), ".yaml")
Expand Down Expand Up @@ -327,7 +325,7 @@ func (i *Imager) buildCmdline() error {
cmdline.SetAll(p.KernelArgs().Strings())

// board kernel args
// TODO: check if supports overlay quirk
// TODO: frezbo: check if supports overlay quirk
if i.prof.Board != "" {
var b talosruntime.Board

Expand All @@ -342,7 +340,7 @@ func (i *Imager) buildCmdline() error {

// overlay kernel args
if i.overlayInstaller != nil {
options, optsErr := i.overlayInstaller.GetOptions(i.prof.Overlay.Options)
options, optsErr := i.overlayInstaller.GetOptions(i.prof.Overlay.ExtraOptions)
if optsErr != nil {
return optsErr
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/imager/internal/overlay/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import (
"io"
"os/exec"

"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"

"github.com/siderolabs/talos/pkg/machinery/overlay"
)

var _ overlay.Installer = (*Options)(nil)
var _ overlay.Installer[overlay.ExtraOptions] = (*Options)(nil)

// Options executor options.
type Options struct {
Expand All @@ -31,7 +31,7 @@ func New(commandPath string) *Options {
}

// GetOptions returns the options for the overlay installer.
func (o *Options) GetOptions(extra overlay.InstallExtraOptions) (overlay.Options, error) {
func (o *Options) GetOptions(extra overlay.ExtraOptions) (overlay.Options, error) {
// parse extra as yaml
extraYAML, err := yaml.Marshal(extra)
if err != nil {
Expand All @@ -53,7 +53,7 @@ func (o *Options) GetOptions(extra overlay.InstallExtraOptions) (overlay.Options
}

// Install installs the overlay.
func (o *Options) Install(options overlay.InstallOptions) error {
func (o *Options) Install(options overlay.InstallOptions[overlay.ExtraOptions]) error {
optionsBytes, err := yaml.Marshal(&options)
if err != nil {
return fmt.Errorf("failed to marshal options: %w", err)
Expand Down
65 changes: 64 additions & 1 deletion pkg/imager/out.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/siderolabs/go-pointer"
"github.com/siderolabs/go-procfs/procfs"
"gopkg.in/yaml.v3"

"github.com/siderolabs/talos/cmd/installer/pkg/install"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options"
Expand Down Expand Up @@ -282,6 +283,12 @@ func (i *Imager) buildImage(ctx context.Context, path string, printf func(string
Printf: printf,
}

if i.overlayInstaller != nil {
opts.OverlayInstaller = i.overlayInstaller
opts.ExtraOptions = i.prof.Overlay.ExtraOptions
opts.ScratchDir = i.tempDir
}

if opts.Board == "" {
opts.Board = constants.BoardNone
}
Expand All @@ -298,7 +305,7 @@ func (i *Imager) buildImage(ctx context.Context, path string, printf func(string
return nil
}

//nolint:gocyclo
//nolint:gocyclo,cyclop
func (i *Imager) outInstaller(ctx context.Context, path string, report *reporter.Reporter) error {
printf := progressPrintf(report, reporter.Update{Message: "building installer...", Status: reporter.StatusRunning})

Expand Down Expand Up @@ -415,6 +422,62 @@ func (i *Imager) outInstaller(ctx context.Context, path string, report *reporter
return fmt.Errorf("failed to append artifacts layer: %w", err)
}

if i.overlayInstaller != nil {
extraOpts, internalErr := yaml.Marshal(i.prof.Overlay.ExtraOptions)
if internalErr != nil {
return fmt.Errorf("failed to marshal extra options: %w", internalErr)
}

if internalErr = os.WriteFile(filepath.Join(i.tempDir, constants.ImagerOverlayExtraOptionsPath), extraOpts, 0o644); internalErr != nil {
return fmt.Errorf("failed to write extra options yaml: %w", internalErr)
}

printf("generating overlay installer layer")

var overlayArtifacts []filemap.File

for _, extraArtifact := range []struct {
sourcePath string
imagePath string
}{
{
sourcePath: filepath.Join(i.tempDir, constants.ImagerOverlayArtifactsPath),
imagePath: constants.ImagerOverlayArtifactsPath,
},
{
sourcePath: filepath.Join(i.tempDir, constants.ImagerOverlayInstallersPath, i.prof.Overlay.Name),
imagePath: constants.ImagerOverlayInstallerDefaultPath,
},
{
sourcePath: filepath.Join(i.tempDir, constants.ImagerOverlayExtraOptionsPath),
imagePath: constants.ImagerOverlayExtraOptionsPath,
},
} {
if extraArtifact.sourcePath == "" {
continue
}

var extraFiles []filemap.File

extraFiles, err = filemap.Walk(extraArtifact.sourcePath, extraArtifact.imagePath)
if err != nil {
return fmt.Errorf("failed to walk extra artifact %s: %w", extraArtifact.sourcePath, err)
}

overlayArtifacts = append(overlayArtifacts, extraFiles...)
}

overlayArtifactsLayer, internalErr := filemap.Layer(overlayArtifacts)
if internalErr != nil {
return fmt.Errorf("failed to create overlay artifacts layer: %w", internalErr)
}

newInstallerImg, internalErr = mutate.AppendLayers(newInstallerImg, overlayArtifactsLayer)
if internalErr != nil {
return fmt.Errorf("failed to append overlay artifacts layer: %w", internalErr)
}
}

ref, err := name.ParseReference(i.prof.Input.BaseInstaller.ImageRef)
if err != nil {
return fmt.Errorf("failed to parse image reference: %w", err)
Expand Down
Loading

0 comments on commit b00f301

Please sign in to comment.