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 Feb 29, 2024
1 parent 8125e75 commit 2a2e2ce
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 37 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")
}
44 changes: 40 additions & 4 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
ScratchDir string
ExtraOptions overlay.ExtraOptions

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

// Install installs Talos.
func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options) (err error) {
//
//nolint:gocyclo
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,7 +88,7 @@ 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
}

Expand All @@ -91,7 +100,7 @@ func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options)

var b runtime.Board

b, err = board.NewBoard(opts.Board)
b, err := board.NewBoard(opts.Board)
if err != nil {
return err
}
Expand All @@ -101,7 +110,7 @@ func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options)
cmdline.SetAll(b.KernelArgs().Strings())
}

if err = cmdline.AppendAll(
if err := cmdline.AppendAll(
opts.ExtraKernelArgs,
procfs.WithOverwriteArgs("console"),
procfs.WithOverwriteArgs(constants.KernelParamPlatform),
Expand All @@ -110,6 +119,22 @@ func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options)
return err
}

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

var extraOptions overlay.ExtraOptions

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

opts.OverlayInstaller = executor.New(constants.ImagerOverlayInstallerDefaultPath)
opts.ExtraOptions = extraOptions
}

i, err := NewInstaller(ctx, cmdline, mode, opts)
if err != nil {
return err
Expand Down Expand Up @@ -302,6 +327,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{
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
18 changes: 8 additions & 10 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 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 @@ -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
72 changes: 66 additions & 6 deletions 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 @@ -261,11 +262,14 @@ func (i *Imager) buildImage(ctx context.Context, path string, printf func(string
scratchSpace := filepath.Join(i.tempDir, "image")

opts := &install.Options{
Disk: loDevice,
Platform: i.prof.Platform,
Arch: i.prof.Arch,
Board: i.prof.Board,
MetaValues: install.FromMeta(i.prof.Customization.MetaContents),
Disk: loDevice,
Platform: i.prof.Platform,
Arch: i.prof.Arch,
Board: i.prof.Board,
MetaValues: install.FromMeta(i.prof.Customization.MetaContents),
OverlayInstaller: i.overlayInstaller,
ExtraOptions: i.prof.Overlay.ExtraOptions,
ScratchDir: i.tempDir,

ImageSecureboot: i.prof.SecureBootEnabled(),
Version: i.prof.Version,
Expand Down Expand Up @@ -298,7 +302,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 +419,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
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"io"
"os/exec"

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

"github.com/siderolabs/talos/pkg/machinery/overlay"
)
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 @@ -40,7 +40,7 @@ func (o *Options) GetOptions(extra overlay.InstallExtraOptions) (overlay.Options

out, err := o.execute(bytes.NewReader(extraYAML), "get-options")
if err != nil {
return overlay.Options{}, fmt.Errorf("failed to run overlay installer: %w", err)
return overlay.Options{}, err
}

var options overlay.Options
Expand All @@ -60,7 +60,7 @@ func (o *Options) Install(options overlay.InstallOptions) error {
}

if _, err := o.execute(bytes.NewReader(optionsBytes), "install"); err != nil {
return fmt.Errorf("failed to run overlay installer: %w", err)
return err
}

return nil
Expand Down
8 changes: 4 additions & 4 deletions pkg/imager/profile/deep_copy.generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pkg/imager/profile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"gopkg.in/yaml.v3"

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

//go:generate deep-copy -type Profile -header-file ../../../hack/boilerplate.txt -o deep_copy.generated.go .
Expand Down Expand Up @@ -50,7 +51,7 @@ type OverlayOptions struct {
// Image to use for the overlay.
Image ContainerAsset `yaml:"image"`
// Options for the overlay.
Options map[string]any `yaml:"options,omitempty"`
overlay.ExtraOptions `yaml:"options,omitempty"`
}

// CustomizationProfile describes customizations that can be applied to the image.
Expand Down
Loading

0 comments on commit 2a2e2ce

Please sign in to comment.