mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-29 15:31:12 +01:00
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:
parent
2d3ac925ea
commit
44f59a8049
2
Makefile
2
Makefile
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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})
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -51,6 +51,7 @@ const (
|
||||
OutKindKernel // kernel
|
||||
OutKindInitramfs // initramfs
|
||||
OutKindUKI // uki
|
||||
OutKindCmdline // cmdline
|
||||
)
|
||||
|
||||
//go:generate enumer -type OutFormat -linecomment -text
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user