feat: improve imager APIs

* report the final output path of the asset
* allow 'cmdline' output (just to get the kernel cmdline, e.g. for PXE
  booting)
* support pre-pulled container images for extensions

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
Andrey Smirnov 2023-08-17 22:12:36 +04:00
parent 2d3ac925ea
commit 44f59a8049
No known key found for this signature in database
GPG Key ID: FE042E3D4085A811
10 changed files with 78 additions and 33 deletions

View File

@ -326,7 +326,7 @@ secureboot-installer: ## Builds UEFI only installer which uses UKI and push it t
@$(MAKE) image-secureboot-installer IMAGER_ARGS="--base-installer-image $(REGISTRY_AND_USERNAME)/installer:$(IMAGE_TAG)"
@for platform in $(subst $(,),$(space),$(PLATFORM)); do \
arch=$$(basename "$${platform}") && \
crane push $(ARTIFACTS)/metal-$${arch}-secureboot-installer.tar $(REGISTRY_AND_USERNAME)/installer:$(IMAGE_TAG)-$${arch}-secureboot ; \
crane push $(ARTIFACTS)/installer-$${arch}-secureboot.tar $(REGISTRY_AND_USERNAME)/installer:$(IMAGE_TAG)-$${arch}-secureboot ; \
done
.PHONY: talosctl-cni-bundle

View File

@ -105,7 +105,7 @@ var rootCmd = &cobra.Command{
return err
}
if err = imager.Execute(ctx, cmdFlags.OutputPath, report); err != nil {
if _, err = imager.Execute(ctx, cmdFlags.OutputPath, report); err != nil {
report.Report(reporter.Update{
Message: err.Error(),
Status: reporter.StatusError,

View File

@ -80,12 +80,10 @@ func New(prof profile.Profile) (*Imager, error) {
// Execute image generation.
//
//nolint:gocyclo,cyclop
func (i *Imager) Execute(ctx context.Context, outputPath string, report *reporter.Reporter) error {
var err error
func (i *Imager) Execute(ctx context.Context, outputPath string, report *reporter.Reporter) (outputAssetPath string, err error) {
i.tempDir, err = os.MkdirTemp("", "imager")
if err != nil {
return fmt.Errorf("failed to create temporary directory: %w", err)
return "", fmt.Errorf("failed to create temporary directory: %w", err)
}
defer os.RemoveAll(i.tempDir) //nolint:errcheck
@ -97,17 +95,17 @@ func (i *Imager) Execute(ctx context.Context, outputPath string, report *reporte
// 0. Dump the profile.
if err = i.prof.Dump(os.Stderr); err != nil {
return err
return "", err
}
// 1. Transform `initramfs.xz` with system extensions
if err = i.buildInitramfs(ctx, report); err != nil {
return err
return "", err
}
// 2. Prepare kernel arguments.
if err = i.buildCmdline(); err != nil {
return err
return "", err
}
report.Report(reporter.Update{
@ -118,12 +116,12 @@ func (i *Imager) Execute(ctx context.Context, outputPath string, report *reporte
// 3. Build UKI if Secure Boot is enabled.
if i.prof.SecureBootEnabled() {
if err = i.buildUKI(report); err != nil {
return err
return "", err
}
}
// 4. Build the output.
outputAssetPath := filepath.Join(outputPath, i.prof.OutputPath())
outputAssetPath = filepath.Join(outputPath, i.prof.OutputPath())
switch i.prof.Output.Kind {
case profile.OutKindISO:
@ -134,6 +132,8 @@ func (i *Imager) Execute(ctx context.Context, outputPath string, report *reporte
err = i.outUKI(outputAssetPath, report)
case profile.OutKindInitramfs:
err = i.outInitramfs(outputAssetPath, report)
case profile.OutKindCmdline:
err = i.outCmdline(outputAssetPath)
case profile.OutKindImage:
err = i.outImage(ctx, outputAssetPath, report)
case profile.OutKindInstaller:
@ -141,11 +141,11 @@ func (i *Imager) Execute(ctx context.Context, outputPath string, report *reporte
case profile.OutKindUnknown:
fallthrough
default:
return fmt.Errorf("unknown output kind: %s", i.prof.Output.Kind)
return "", fmt.Errorf("unknown output kind: %s", i.prof.Output.Kind)
}
if err != nil {
return err
return "", err
}
report.Report(reporter.Update{
@ -157,7 +157,7 @@ func (i *Imager) Execute(ctx context.Context, outputPath string, report *reporte
switch i.prof.Output.OutFormat {
case profile.OutFormatRaw:
// do nothing
return nil
return outputAssetPath, nil
case profile.OutFormatXZ:
return i.postProcessXz(outputAssetPath, report)
case profile.OutFormatGZ:
@ -167,7 +167,7 @@ func (i *Imager) Execute(ctx context.Context, outputPath string, report *reporte
case profile.OutFormatUnknown:
fallthrough
default:
return fmt.Errorf("unknown output format: %s", i.prof.Output.OutFormat)
return "", fmt.Errorf("unknown output format: %s", i.prof.Output.OutFormat)
}
}
@ -185,6 +185,11 @@ func (i *Imager) buildInitramfs(ctx context.Context, report *reporter.Reporter)
return nil
}
if i.prof.Output.Kind == profile.OutKindCmdline || i.prof.Output.Kind == profile.OutKindKernel {
// these outputs don't use initramfs image
return nil
}
printf := progressPrintf(report, reporter.Update{Message: "rebuilding initramfs with system extensions...", Status: reporter.StatusRunning})
// copy the initramfs to a temporary location, as it's going to be modified during the extension build process

View File

@ -8,6 +8,7 @@ import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"time"
@ -67,6 +68,10 @@ func (i *Imager) outUKI(path string, report *reporter.Reporter) error {
return nil
}
func (i *Imager) outCmdline(path string) error {
return os.WriteFile(path, []byte(i.cmdline), 0o644)
}
func (i *Imager) outISO(path string, report *reporter.Reporter) error {
printf := progressPrintf(report, reporter.Update{Message: "building ISO...", Status: reporter.StatusRunning})

View File

@ -14,51 +14,51 @@ import (
"github.com/siderolabs/talos/pkg/reporter"
)
func (i *Imager) postProcessTar(filename string, report *reporter.Reporter) error {
func (i *Imager) postProcessTar(filename string, report *reporter.Reporter) (string, error) {
report.Report(reporter.Update{Message: "processing .tar.gz", Status: reporter.StatusRunning})
dir := filepath.Dir(filename)
src := "disk.raw"
if err := os.Rename(filename, filepath.Join(dir, src)); err != nil {
return err
return "", err
}
outPath := filename + ".tar.gz"
if _, err := cmd.Run("tar", "-cvf", outPath, "-C", dir, "--sparse", "--use-compress-program=pigz -6", src); err != nil {
return err
return "", err
}
if err := os.Remove(filepath.Join(dir, src)); err != nil {
return err
return "", err
}
report.Report(reporter.Update{Message: fmt.Sprintf("archive is ready: %s", outPath), Status: reporter.StatusSucceeded})
return nil
return outPath, nil
}
func (i *Imager) postProcessGz(filename string, report *reporter.Reporter) error {
func (i *Imager) postProcessGz(filename string, report *reporter.Reporter) (string, error) {
report.Report(reporter.Update{Message: "compressing .gz", Status: reporter.StatusRunning})
if _, err := cmd.Run("pigz", "-6", "-f", filename); err != nil {
return err
return "", err
}
report.Report(reporter.Update{Message: fmt.Sprintf("compression done: %s.gz", filename), Status: reporter.StatusSucceeded})
return nil
return filename + ".gz", nil
}
func (i *Imager) postProcessXz(filename string, report *reporter.Reporter) error {
func (i *Imager) postProcessXz(filename string, report *reporter.Reporter) (string, error) {
report.Report(reporter.Update{Message: "compressing .xz", Status: reporter.StatusRunning})
if _, err := cmd.Run("xz", "-0", "-f", "-T", "0", filename); err != nil {
return err
return "", err
}
report.Report(reporter.Update{Message: fmt.Sprintf("compression done: %s.xz", filename), Status: reporter.StatusSucceeded})
return nil
return filename + ".xz", nil
}

View File

@ -48,6 +48,10 @@ type FileAsset struct {
type ContainerAsset struct {
// ImageRef is a reference to the container image.
ImageRef string `yaml:"imageRef"`
// TarballPath is a path to the .tar format container image contents.
//
// If TarballPath is set, ImageRef is ignored.
TarballPath string `yaml:"tarballPath,omitempty"`
}
// SecureBootAssets describes secureboot assets.
@ -144,6 +148,10 @@ func fileExists(path string) bool {
// Pull the container asset to the path.
func (c *ContainerAsset) Pull(ctx context.Context, arch string, printf func(string, ...any)) (v1.Image, error) {
if c.TarballPath != "" {
return nil, fmt.Errorf("pulling tarball container image is not supported")
}
printf("pulling %s...", c.ImageRef)
img, err := crane.Pull(c.ImageRef, crane.WithPlatform(&v1.Platform{
@ -159,6 +167,19 @@ func (c *ContainerAsset) Pull(ctx context.Context, arch string, printf func(stri
// Extract the container asset to the path.
func (c *ContainerAsset) Extract(ctx context.Context, destination, arch string, printf func(string, ...any)) error {
if c.TarballPath != "" {
in, err := os.Open(c.TarballPath)
if err != nil {
return err
}
defer in.Close() //nolint:errcheck
printf("extracting %s...", c.TarballPath)
return archiver.Untar(ctx, in, destination)
}
img, err := c.Pull(ctx, arch, printf)
if err != nil {
return err

View File

@ -51,6 +51,7 @@ const (
OutKindKernel // kernel
OutKindInitramfs // initramfs
OutKindUKI // uki
OutKindCmdline // cmdline
)
//go:generate enumer -type OutFormat -linecomment -text

View File

@ -6,9 +6,9 @@ import (
"fmt"
)
const _OutputKindName = "unknownisoimageinstallerkernelinitramfsuki"
const _OutputKindName = "unknownisoimageinstallerkernelinitramfsukicmdline"
var _OutputKindIndex = [...]uint8{0, 7, 10, 15, 24, 30, 39, 42}
var _OutputKindIndex = [...]uint8{0, 7, 10, 15, 24, 30, 39, 42, 49}
func (i OutputKind) String() string {
if i < 0 || i >= OutputKind(len(_OutputKindIndex)-1) {
@ -17,7 +17,7 @@ func (i OutputKind) String() string {
return _OutputKindName[_OutputKindIndex[i]:_OutputKindIndex[i+1]]
}
var _OutputKindValues = []OutputKind{0, 1, 2, 3, 4, 5, 6}
var _OutputKindValues = []OutputKind{0, 1, 2, 3, 4, 5, 6, 7}
var _OutputKindNameToValueMap = map[string]OutputKind{
_OutputKindName[0:7]: 0,
@ -27,6 +27,7 @@ var _OutputKindNameToValueMap = map[string]OutputKind{
_OutputKindName[24:30]: 4,
_OutputKindName[30:39]: 5,
_OutputKindName[39:42]: 6,
_OutputKindName[42:49]: 7,
}
// OutputKindString retrieves an enum value from the enum constants string name.

View File

@ -55,7 +55,7 @@ func (p *Profile) SecureBootEnabled() bool {
// Validate the profile.
//
//nolint:gocyclo
//nolint:gocyclo,cyclop
func (p *Profile) Validate() error {
if p.Arch != "amd64" && p.Arch != "arm64" {
return fmt.Errorf("invalid arch %q", p.Arch)
@ -76,6 +76,8 @@ func (p *Profile) Validate() error {
return fmt.Errorf("unknown output kind")
case OutKindISO:
// ISO supports all kinds of customization
case OutKindCmdline:
// cmdline supports all kinds of customization
case OutKindImage:
// Image supports all kinds of customization
if p.Output.ImageOptions.DiskSize == 0 {
@ -111,6 +113,8 @@ func (p *Profile) Validate() error {
}
// OutputPath generates the output path for the profile.
//
//nolint:gocyclo
func (p *Profile) OutputPath() string {
path := p.Platform
@ -132,13 +136,21 @@ func (p *Profile) OutputPath() string {
case OutKindImage:
path += "." + p.Output.ImageOptions.DiskFormat.String()
case OutKindInstaller:
path += "-installer.tar"
path = "installer-" + p.Arch
if p.SecureBootEnabled() {
path += "-secureboot"
}
path += ".tar"
case OutKindKernel:
path = "kernel-" + p.Arch
case OutKindInitramfs:
path = "initramfs-" + path + ".xz"
case OutKindUKI:
path += "-uki.efi"
case OutKindCmdline:
path = "cmdline-" + path
}
return path

View File

@ -139,13 +139,13 @@ skipped initramfs rebuild (no system extensions)
kernel command line: talos.platform=metal console=ttyS0 console=tty0 init_on_alloc=1 slab_nomerge pti=on consoleblank=0 nvme_core.io_timeout=4294967295 printk.devkmsg=on ima_template=ima-ng ima_appraise=fix ima_hash=sha512 lockdown=confidentiality
UKI ready
installer container image ready
output asset path: /out/metal-amd64-secureboot-installer.tar
output asset path: /out/installer-amd64-secureboot.tar
```
The generated container image should be pushed to some container registry which Talos can access during the installation, e.g.:
```shell
crane push _out/metal-amd64-secureboot-installer.tar ghcr.io/<user>/installer-amd64-secureboot:{{< release >}}
crane push _out/installer-amd64-secureboot.tar ghcr.io/<user>/installer-amd64-secureboot:{{< release >}}
```
The generated ISO and installer images might be further customized with system extensions, extra kernel command line arguments, etc.