From e0f383598e2f285c04264e9a3787fcdcd56add85 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Fri, 4 Aug 2023 23:11:16 +0400 Subject: [PATCH] chore: clean up the output of the `imager` Use `Progress`, and options to pass around the way messages are written. Fixed some tiny issues in the code, but otherwise no functional changes. To make colored output work with `docker run`, switched back image generation to use volume mount for output (old mode is still functioning, but it's not the default, and it works when docker is not running on the same host). Signed-off-by: Andrey Smirnov --- Makefile | 4 +- cmd/installer/cmd/imager/root.go | 25 +++- cmd/installer/pkg/install/install.go | 10 +- cmd/installer/pkg/install/manifest.go | 29 ++-- cmd/installer/pkg/install/manifest_test.go | 37 ++--- cmd/installer/pkg/install/target.go | 10 +- .../app/machined/pkg/runtime/sequencer.go | 2 +- .../v1alpha1/bootloader/grub/encode.go | 5 +- .../v1alpha1/bootloader/grub/grub_test.go | 2 +- .../v1alpha1/bootloader/grub/install.go | 15 +- .../v1alpha1/bootloader/grub/revert.go | 3 +- .../v1alpha1/bootloader/options/options.go | 3 + .../v1alpha1/bootloader/sdboot/sdboot.go | 5 +- .../v1alpha1/v1alpha1_sequencer_tasks.go | 3 +- internal/pkg/mount/system.go | 2 +- internal/pkg/partition/format.go | 11 +- internal/pkg/partition/format_test.go | 2 +- internal/pkg/partition/partition.go | 8 +- internal/pkg/secureboot/uki/sbat.go | 3 - internal/pkg/secureboot/uki/uki.go | 10 +- pkg/imager/extensions/rebuild.go | 4 +- pkg/imager/imager.go | 84 ++++++++---- pkg/imager/iso/grub.go | 10 +- pkg/imager/iso/uefi.go | 10 +- pkg/imager/out.go | 128 ++++++++++++++---- pkg/imager/ova/ova.go | 6 +- pkg/imager/post.go | 35 +++-- pkg/imager/profile/input.go | 9 +- pkg/imager/profile/profile.go | 2 +- pkg/imager/progress.go | 28 ++++ pkg/imager/qemuimg/qemuimg.go | 4 +- pkg/imager/utils/copy.go | 7 +- pkg/imager/utils/raw.go | 5 +- pkg/imager/utils/touch.go | 5 +- 34 files changed, 355 insertions(+), 171 deletions(-) create mode 100644 pkg/imager/progress.go diff --git a/Makefile b/Makefile index 2fba5a869..cfab41b5f 100644 --- a/Makefile +++ b/Makefile @@ -300,7 +300,7 @@ image-%: ## Builds the specified image. Valid options are aws, azure, digital-oc @docker pull $(REGISTRY_AND_USERNAME)/imager:$(IMAGE_TAG) @for platform in $(subst $(,),$(space),$(PLATFORM)); do \ arch=$$(basename "$${platform}") && \ - docker run --rm -v /dev:/dev -v $(PWD)/$(ARTIFACTS):/secureboot:ro --network=host --privileged $(REGISTRY_AND_USERNAME)/imager:$(IMAGE_TAG) $* --arch $$arch --tar-to-stdout $(IMAGER_ARGS) | tar xz -C $(ARTIFACTS) ; \ + docker run --rm -t -v /dev:/dev -v $(PWD)/$(ARTIFACTS):/secureboot:ro -v $(PWD)/$(ARTIFACTS):/out --network=host --privileged $(REGISTRY_AND_USERNAME)/imager:$(IMAGE_TAG) $* --arch $$arch $(IMAGER_ARGS) ; \ done images-essential: image-aws image-gcp image-metal secureboot-installer ## Builds only essential images used in the CI (AWS, GCP, and Metal). @@ -309,7 +309,7 @@ images: image-aws image-azure image-digital-ocean image-exoscale image-gcp image sbc-%: ## Builds the specified SBC image. Valid options are rpi_generic, rock64, bananapi_m64, libretech_all_h3_cc_h5, rockpi_4, rockpi_4c, pine64, jetson_nano and nanopi_r4s (e.g. sbc-rpi_generic) @docker pull $(REGISTRY_AND_USERNAME)/imager:$(IMAGE_TAG) - @docker run --rm -v /dev:/dev --network=host --privileged $(REGISTRY_AND_USERNAME)/imager:$(IMAGE_TAG) $* --arch arm64 --tar-to-stdout $(IMAGER_ARGS) | tar xz -C $(ARTIFACTS) + @docker run --rm -t -v /dev:/dev -v $(PWD)/$(ARTIFACTS):/out --network=host --privileged $(REGISTRY_AND_USERNAME)/imager:$(IMAGE_TAG) $* --arch arm64 $(IMAGER_ARGS) sbcs: sbc-rpi_generic sbc-rock64 sbc-bananapi_m64 sbc-libretech_all_h3_cc_h5 sbc-rockpi_4 sbc-rockpi_4c sbc-pine64 sbc-jetson_nano sbc-nanopi_r4s ## Builds all known SBC images (Raspberry Pi 4, Rock64, Banana Pi M64, Radxa ROCK Pi 4, Radxa ROCK Pi 4c, Pine64, Libre Computer Board ALL-H3-CC, Jetson Nano and Nano Pi R4S). diff --git a/cmd/installer/cmd/imager/root.go b/cmd/installer/cmd/imager/root.go index 44cb2db60..dc6cb117a 100644 --- a/cmd/installer/cmd/imager/root.go +++ b/cmd/installer/cmd/imager/root.go @@ -7,7 +7,6 @@ package imager import ( "context" - "fmt" "os" "runtime" @@ -21,6 +20,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/reporter" ) var cmdFlags struct { @@ -38,12 +38,19 @@ var cmdFlags struct { // rootCmd represents the base command when called without any subcommands. var rootCmd = &cobra.Command{ - Use: "imager |-", - Short: "Generate various boot assets and images.", - Long: ``, - Args: cobra.ExactArgs(1), + Use: "imager |-", + Short: "Generate various boot assets and images.", + Long: ``, + Args: cobra.ExactArgs(1), + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { return cli.WithContext(context.Background(), func(ctx context.Context) error { + report := reporter.New() + report.Report(reporter.Update{ + Message: "assembling the finalized profile...", + Status: reporter.StatusRunning, + }) + baseProfile := args[0] var prof profile.Profile @@ -98,7 +105,12 @@ var rootCmd = &cobra.Command{ return err } - if err = imager.Execute(ctx, cmdFlags.OutputPath); err != nil { + if err = imager.Execute(ctx, cmdFlags.OutputPath, report); err != nil { + report.Report(reporter.Update{ + Message: err.Error(), + Status: reporter.StatusError, + }) + return err } @@ -115,7 +127,6 @@ var rootCmd = &cobra.Command{ // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { if err := rootCmd.Execute(); err != nil { - fmt.Fprintln(os.Stderr, err) os.Exit(1) } } diff --git a/cmd/installer/pkg/install/install.go b/cmd/installer/pkg/install/install.go index 6a158dafc..580fb892a 100644 --- a/cmd/installer/pkg/install/install.go +++ b/cmd/installer/pkg/install/install.go @@ -42,6 +42,7 @@ type Options struct { ImageSecureboot bool Version string BootAssets bootloaderoptions.BootAssets + Printf func(string, ...any) } // Mode is the install mode. @@ -107,7 +108,7 @@ func Install(ctx context.Context, p runtime.Platform, mode Mode, opts *Options) return err } - log.Printf("installation of %s complete", version.Tag) + i.options.Printf("installation of %s complete", version.Tag) return nil } @@ -132,6 +133,10 @@ func NewInstaller(ctx context.Context, cmdline *procfs.Cmdline, mode Mode, opts i.options.Version = version.Tag } + if i.options.Printf == nil { + i.options.Printf = log.Printf + } + if !i.options.Zero { i.bootloader, err = bootloader.Probe(ctx, i.options.Disk) if err != nil && !os.IsNotExist(err) { @@ -258,6 +263,7 @@ func (i *Installer) Install(ctx context.Context, mode Mode) (err error) { Version: i.options.Version, ImageMode: mode.IsImage(), BootAssets: i.options.BootAssets, + Printf: i.options.Printf, }); err != nil { return err } @@ -270,7 +276,7 @@ func (i *Installer) Install(ctx context.Context, mode Mode) (err error) { return err } - log.Printf("installing U-Boot for %q", b.Name()) + i.options.Printf("installing U-Boot for %q", b.Name()) if err = b.Install(i.options.Disk); err != nil { return err diff --git a/cmd/installer/pkg/install/manifest.go b/cmd/installer/pkg/install/manifest.go index 27a00f0e9..fd226ad4a 100644 --- a/cmd/installer/pkg/install/manifest.go +++ b/cmd/installer/pkg/install/manifest.go @@ -9,7 +9,6 @@ import ( "context" "errors" "fmt" - "log" "os" "path/filepath" "strings" @@ -33,6 +32,8 @@ type Manifest struct { Devices map[string]Device Targets map[string][]*Target LegacyBIOSSupport bool + + Printf func(string, ...any) } // Device represents device options. @@ -53,6 +54,8 @@ func NewManifest(mode Mode, uefiOnlyBoot bool, bootLoaderPresent bool, opts *Opt Devices: map[string]Device{}, Targets: map[string][]*Target{}, LegacyBIOSSupport: opts.LegacyBIOSSupport, + + Printf: opts.Printf, } if opts.Board != constants.BoardNone { @@ -248,7 +251,7 @@ func (m *Manifest) executeOnDevice(device Device, targets []*Target) (err error) if device.Zero { if err = partition.Format(device.Device, &partition.FormatOptions{ FileSystemType: partition.FilesystemTypeNone, - }); err != nil { + }, m.Printf); err != nil { return err } } @@ -272,7 +275,7 @@ func (m *Manifest) executeOnDevice(device Device, targets []*Target) (err error) return err } - log.Printf("creating new partition table on %s", device.Device) + m.Printf("creating new partition table on %s", device.Device) gptOpts := []gpt.Option{ gpt.WithMarkMBRBootable(m.LegacyBIOSSupport), @@ -287,8 +290,8 @@ func (m *Manifest) executeOnDevice(device Device, targets []*Target) (err error) return err } - log.Printf("logical/physical block size: %d/%d", pt.Header().LBA.LogicalBlockSize, pt.Header().LBA.PhysicalBlockSize) - log.Printf("minimum/optimal I/O size: %d/%d", pt.Header().LBA.MinimalIOSize, pt.Header().LBA.OptimalIOSize) + m.Printf("logical/physical block size: %d/%d", pt.Header().LBA.LogicalBlockSize, pt.Header().LBA.PhysicalBlockSize) + m.Printf("minimum/optimal I/O size: %d/%d", pt.Header().LBA.MinimalIOSize, pt.Header().LBA.OptimalIOSize) if err = pt.Write(); err != nil { return err @@ -309,7 +312,7 @@ func (m *Manifest) executeOnDevice(device Device, targets []*Target) (err error) if !created { if device.ResetPartitionTable { - log.Printf("resetting partition table on %s", device.Device) + m.Printf("resetting partition table on %s", device.Device) // TODO: how should it work with zero option above? if err = bd.Reset(); err != nil { @@ -343,7 +346,7 @@ func (m *Manifest) executeOnDevice(device Device, targets []*Target) (err error) // delete all partitions which are not skipped for _, part := range pt.Partitions().Items() { if _, ok := keepPartitions[part.Name]; !ok { - log.Printf("deleting partition %s", part.Name) + m.Printf("deleting partition %s", part.Name) if err = pt.Delete(part); err != nil { return err @@ -363,7 +366,7 @@ func (m *Manifest) executeOnDevice(device Device, targets []*Target) (err error) } for i, target := range targets { - if err = target.partition(pt, i); err != nil { + if err = target.partition(pt, i, m.Printf); err != nil { return fmt.Errorf("failed to partition device: %w", err) } } @@ -376,7 +379,7 @@ func (m *Manifest) executeOnDevice(device Device, targets []*Target) (err error) target := target err = retry.Constant(time.Minute, retry.WithUnits(100*time.Millisecond)).Retry(func() error { - e := target.Format() + e := target.Format(m.Printf) if e != nil { if strings.Contains(e.Error(), "No such file or directory") { // workaround problem with partition device not being visible immediately after partitioning @@ -422,7 +425,7 @@ func (m *Manifest) preserveContents(device Device, targets []*Target) (err error if bd, err = blockdevice.Open(device.Device); err != nil { // failed to open the block device, probably it's damaged? - log.Printf("warning: skipping preserve contents on %q as block device failed: %s", device.Device, err) + m.Printf("warning: skipping preserve contents on %q as block device failed: %s", device.Device, err) return nil } @@ -432,7 +435,7 @@ func (m *Manifest) preserveContents(device Device, targets []*Target) (err error pt, err := bd.PartitionTable() if err != nil { - log.Printf("warning: skipping preserve contents on %q as partition table failed: %s", device.Device, err) + m.Printf("warning: skipping preserve contents on %q as partition table failed: %s", device.Device, err) return nil } @@ -473,13 +476,13 @@ func (m *Manifest) preserveContents(device Device, targets []*Target) (err error } if sourcePart == nil { - log.Printf("warning: failed to preserve contents of %q on %q, as source partition wasn't found", target.Label, device.Device) + m.Printf("warning: failed to preserve contents of %q on %q, as source partition wasn't found", target.Label, device.Device) continue } if err = target.SaveContents(device, sourcePart, fileSystemType, fnmatchFilters); err != nil { - log.Printf("warning: failed to preserve contents of %q on %q: %s", target.Label, device.Device, err) + m.Printf("warning: failed to preserve contents of %q on %q: %s", target.Label, device.Device, err) } } diff --git a/cmd/installer/pkg/install/manifest_test.go b/cmd/installer/pkg/install/manifest_test.go index 9338046eb..4a2d53fd1 100644 --- a/cmd/installer/pkg/install/manifest_test.go +++ b/cmd/installer/pkg/install/manifest_test.go @@ -229,9 +229,10 @@ func (suite *manifestSuite) TestExecuteManifestClean() { suite.skipUnderBuildkit() manifest, err := install.NewManifest(install.ModeInstall, false, false, &install.Options{ - Disk: suite.loopbackDevice.Name(), - Force: true, - Board: constants.BoardNone, + Disk: suite.loopbackDevice.Name(), + Force: true, + Board: constants.BoardNone, + Printf: suite.T().Logf, }) suite.Require().NoError(err) @@ -249,9 +250,10 @@ func (suite *manifestSuite) TestExecuteManifestForce() { suite.skipUnderBuildkit() manifest, err := install.NewManifest(install.ModeInstall, false, false, &install.Options{ - Disk: suite.loopbackDevice.Name(), - Force: true, - Board: constants.BoardNone, + Disk: suite.loopbackDevice.Name(), + Force: true, + Board: constants.BoardNone, + Printf: suite.T().Logf, }) suite.Require().NoError(err) @@ -267,10 +269,11 @@ func (suite *manifestSuite) TestExecuteManifestForce() { // reinstall manifest, err = install.NewManifest(install.ModeUpgrade, false, true, &install.Options{ - Disk: suite.loopbackDevice.Name(), - Force: true, - Zero: true, - Board: constants.BoardNone, + Disk: suite.loopbackDevice.Name(), + Force: true, + Zero: true, + Board: constants.BoardNone, + Printf: suite.T().Logf, }) suite.Require().NoError(err) @@ -288,9 +291,10 @@ func (suite *manifestSuite) TestExecuteManifestPreserve() { suite.skipUnderBuildkit() manifest, err := install.NewManifest(install.ModeInstall, false, false, &install.Options{ - Disk: suite.loopbackDevice.Name(), - Force: true, - Board: constants.BoardNone, + Disk: suite.loopbackDevice.Name(), + Force: true, + Board: constants.BoardNone, + Printf: suite.T().Logf, }) suite.Require().NoError(err) @@ -306,9 +310,10 @@ func (suite *manifestSuite) TestExecuteManifestPreserve() { // reinstall manifest, err = install.NewManifest(install.ModeUpgrade, false, true, &install.Options{ - Disk: suite.loopbackDevice.Name(), - Force: false, - Board: constants.BoardNone, + Disk: suite.loopbackDevice.Name(), + Force: false, + Board: constants.BoardNone, + Printf: suite.T().Logf, }) suite.Require().NoError(err) diff --git a/cmd/installer/pkg/install/target.go b/cmd/installer/pkg/install/target.go index a3a05b695..63aefeea2 100644 --- a/cmd/installer/pkg/install/target.go +++ b/cmd/installer/pkg/install/target.go @@ -200,11 +200,11 @@ func (t *Target) Locate(pt *gpt.GPT) (*gpt.Partition, error) { } // partition creates a new partition on the specified device. -func (t *Target) partition(pt *gpt.GPT, pos int) (err error) { +func (t *Target) partition(pt *gpt.GPT, pos int, printf func(string, ...any)) (err error) { if t.Skip { part := pt.Partitions().FindByName(t.Label) if part != nil { - log.Printf("skipped %s (%s) size %d blocks", t.PartitionName, t.Label, part.Length()) + printf("skipped %s (%s) size %d blocks", t.PartitionName, t.Label, part.Length()) t.PartitionName, err = part.Path() if err != nil { @@ -220,7 +220,7 @@ func (t *Target) partition(pt *gpt.GPT, pos int) (err error) { PartitionType: t.PartitionType, Size: t.Size, LegacyBIOSBootable: t.LegacyBIOSBootable, - }) + }, printf) if err != nil { return err } @@ -231,12 +231,12 @@ func (t *Target) partition(pt *gpt.GPT, pos int) (err error) { } // Format creates a filesystem on the device/partition. -func (t *Target) Format() error { +func (t *Target) Format(printf func(string, ...any)) error { if t.Skip { return nil } - return partition.Format(t.PartitionName, t.FormatOptions) + return partition.Format(t.PartitionName, t.FormatOptions, printf) } // GetLabel returns the underlaying partition label. diff --git a/internal/app/machined/pkg/runtime/sequencer.go b/internal/app/machined/pkg/runtime/sequencer.go index 4d5170a9e..9288fb06e 100644 --- a/internal/app/machined/pkg/runtime/sequencer.go +++ b/internal/app/machined/pkg/runtime/sequencer.go @@ -136,7 +136,7 @@ type ResetOptions interface { // PartitionTarget provides interface to the disk partition. type PartitionTarget interface { fmt.Stringer - Format() error + Format(func(string, ...any)) error GetLabel() string } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/encode.go b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/encode.go index f8f62abd3..73af2a442 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/encode.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/encode.go @@ -7,7 +7,6 @@ package grub import ( "bytes" "io" - "log" "os" "path/filepath" "text/template" @@ -43,7 +42,7 @@ menuentry "Reset Talos installation and return to maintenance mode" { ` // Write the grub configuration to the given file. -func (c *Config) Write(path string) error { +func (c *Config) Write(path string, printf func(string, ...any)) error { dir := filepath.Dir(path) if err := os.MkdirAll(dir, os.ModeDir); err != nil { return err @@ -56,7 +55,7 @@ func (c *Config) Write(path string) error { return err } - log.Printf("writing %s to disk", path) + printf("writing %s to disk", path) return os.WriteFile(path, wr.Bytes(), 0o600) } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/grub_test.go b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/grub_test.go index 89f9c2dc8..8c7dd1caa 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/grub_test.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/grub_test.go @@ -103,7 +103,7 @@ func TestWrite(t *testing.T) { config := grub.NewConfig() require.NoError(t, config.Put(grub.BootA, "cmdline A", "v0.0.1")) - err := config.Write(tempFile.Name()) + err := config.Write(tempFile.Name(), t.Logf) assert.NoError(t, err) written, _ := os.ReadFile(tempFile.Name()) diff --git a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/install.go b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/install.go index d9343592a..7b04eca62 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/install.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/install.go @@ -6,14 +6,12 @@ package grub import ( "fmt" - "log" - "os" - "os/exec" "path/filepath" "runtime" "strings" "github.com/siderolabs/go-blockdevice/blockdevice" + "github.com/siderolabs/go-cmd/pkg/cmd" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options" "github.com/siderolabs/talos/pkg/imager/utils" @@ -36,6 +34,7 @@ func (c *Config) Install(options options.InstallOptions) error { options.BootAssets.FillDefaults(options.Arch) if err := utils.CopyFiles( + options.Printf, utils.SourceDestination(options.BootAssets.KernelPath, filepath.Join(constants.BootMountPoint, string(c.Default), constants.KernelAsset)), utils.SourceDestination(options.BootAssets.InitramfsPath, filepath.Join(constants.BootMountPoint, string(c.Default), constants.InitramfsAsset)), ); err != nil { @@ -46,7 +45,7 @@ func (c *Config) Install(options options.InstallOptions) error { return err } - if err := c.Write(ConfigPath); err != nil { + if err := c.Write(ConfigPath, options.Printf); err != nil { return err } @@ -83,13 +82,9 @@ func (c *Config) Install(options options.InstallOptions) error { args = append(args, blk) - log.Printf("executing: grub-install %s", strings.Join(args, " ")) + options.Printf("executing: grub-install %s", strings.Join(args, " ")) - cmd := exec.Command("grub-install", args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err = cmd.Run(); err != nil { + if _, err := cmd.Run("grub-install", args...); err != nil { return fmt.Errorf("failed to install grub: %w", err) } } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/revert.go b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/revert.go index 0a84b7cbd..0fceacb67 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/revert.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub/revert.go @@ -9,6 +9,7 @@ import ( "context" "errors" "fmt" + "log" "os" "path/filepath" @@ -69,7 +70,7 @@ func (c *Config) Revert(ctx context.Context) error { return fmt.Errorf("cannot rollback to %q, label does not exist", "") } - if err := c.Write(ConfigPath); err != nil { + if err := c.Write(ConfigPath, log.Printf); err != nil { return fmt.Errorf("failed to revert bootloader: %v", err) } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options/options.go b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options/options.go index 8026d7c0d..d327e7d03 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options/options.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options/options.go @@ -27,6 +27,9 @@ type InstallOptions struct { // Boot assets to install. BootAssets BootAssets + + // Printf-like function to use. + Printf func(format string, v ...any) } // BootAssets describes the assets to be installed by the booloader. diff --git a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/sdboot/sdboot.go b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/sdboot/sdboot.go index 30a147b37..5f9cb2fe5 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/sdboot/sdboot.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/sdboot/sdboot.go @@ -157,7 +157,7 @@ func (c *Config) Install(options options.InstallOptions) error { continue } - log.Printf("removing old UKI: %s", file) + options.Printf("removing old UKI: %s", file) if err = os.Remove(file); err != nil { return err @@ -167,6 +167,7 @@ func (c *Config) Install(options options.InstallOptions) error { options.BootAssets.FillDefaults(options.Arch) if err := utils.CopyFiles( + options.Printf, utils.SourceDestination(options.BootAssets.UKIPath, filepath.Join(constants.EFIMountPoint, "EFI", "Linux", ukiPath)), utils.SourceDestination(options.BootAssets.SDBootPath, filepath.Join(constants.EFIMountPoint, "EFI", "boot", sdbootFilename)), ); err != nil { @@ -175,6 +176,8 @@ func (c *Config) Install(options options.InstallOptions) error { // don't update EFI variables if we're installing to a loop device if !options.ImageMode { + options.Printf("updating EFI variables") + efiCtx := efivario.NewDefaultContext() // set the new entry as a default one diff --git a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go index 6b4c8700a..fb15b1877 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go @@ -822,6 +822,7 @@ func partitionAndFormatDisks(logger *log.Logger, r runtime.Runtime) error { m := &installer.Manifest{ Devices: map[string]installer.Device{}, Targets: map[string][]*installer.Target{}, + Printf: logger.Printf, } for _, disk := range r.Config().Machine().Disks() { @@ -1618,7 +1619,7 @@ func ResetSystemDiskSpec(_ runtime.Sequence, data any) (runtime.TaskExecutionFun } for _, target := range in.GetSystemDiskTargets() { - if err = target.Format(); err != nil { + if err = target.Format(logger.Printf); err != nil { return fmt.Errorf("failed wiping partition %s: %w", target, err) } } diff --git a/internal/pkg/mount/system.go b/internal/pkg/mount/system.go index 36fdce249..3c498f5f5 100644 --- a/internal/pkg/mount/system.go +++ b/internal/pkg/mount/system.go @@ -167,7 +167,7 @@ func SystemMountPointForLabel(ctx context.Context, device *blockdevice.BlockDevi if !o.MountFlags.Check(SkipIfNoFilesystem) { p.fstype = opts.FileSystemType - return partition.Format(p.source, opts) + return partition.Format(p.source, opts, log.Printf) } return nil diff --git a/internal/pkg/partition/format.go b/internal/pkg/partition/format.go index a94e3f2bb..d3e763618 100644 --- a/internal/pkg/partition/format.go +++ b/internal/pkg/partition/format.go @@ -7,7 +7,6 @@ package partition import ( "fmt" - "log" "github.com/siderolabs/go-blockdevice/blockdevice" @@ -28,13 +27,13 @@ func NewFormatOptions(label string) *FormatOptions { } // Format zeroes the device and formats it using filesystem type provided. -func Format(devname string, t *FormatOptions) error { +func Format(devname string, t *FormatOptions, printf func(string, ...any)) error { if t.FileSystemType == FilesystemTypeNone { - return zeroPartition(devname) + return zeroPartition(devname, printf) } opts := []makefs.Option{makefs.WithForce(t.Force), makefs.WithLabel(t.Label)} - log.Printf("formatting the partition %q as %q with label %q\n", devname, t.FileSystemType, t.Label) + printf("formatting the partition %q as %q with label %q\n", devname, t.FileSystemType, t.Label) switch t.FileSystemType { case FilesystemTypeVFAT: @@ -47,8 +46,8 @@ func Format(devname string, t *FormatOptions) error { } // zeroPartition fills the partition with zeroes. -func zeroPartition(devname string) (err error) { - log.Printf("zeroing out %q", devname) +func zeroPartition(devname string, printf func(string, ...any)) (err error) { + printf("zeroing out %q", devname) part, err := blockdevice.Open(devname, blockdevice.WithExclusiveLock(true)) if err != nil { diff --git a/internal/pkg/partition/format_test.go b/internal/pkg/partition/format_test.go index d17ba1b1c..79ffe22f1 100644 --- a/internal/pkg/partition/format_test.go +++ b/internal/pkg/partition/format_test.go @@ -137,7 +137,7 @@ func (suite *manifestSuite) TestZeroPartition() { err = partition.Format(part, &partition.FormatOptions{ FileSystemType: partition.FilesystemTypeNone, - }) + }, suite.T().Logf) suite.Require().NoError(err) // reading 10 times more than what we wrote should still return 0 since the partition is wiped diff --git a/internal/pkg/partition/partition.go b/internal/pkg/partition/partition.go index 6b0ccd903..fcafa687a 100644 --- a/internal/pkg/partition/partition.go +++ b/internal/pkg/partition/partition.go @@ -6,8 +6,6 @@ package partition import ( - "log" - "github.com/dustin/go-humanize" "github.com/siderolabs/go-blockdevice/blockdevice/partition/gpt" @@ -40,8 +38,8 @@ func Locate(pt *gpt.GPT, label string) (*gpt.Partition, error) { // Partition creates a new partition on the specified device. // Returns the path to the newly created partition. -func Partition(pt *gpt.GPT, pos int, device string, partitionOpts Options) (string, error) { - log.Printf("partitioning %s - %s %q\n", device, partitionOpts.PartitionLabel, humanize.Bytes(partitionOpts.Size)) +func Partition(pt *gpt.GPT, pos int, device string, partitionOpts Options, printf func(string, ...any)) (string, error) { + printf("partitioning %s - %s %q\n", device, partitionOpts.PartitionLabel, humanize.Bytes(partitionOpts.Size)) opts := []gpt.PartitionOption{ gpt.WithPartitionType(partitionOpts.PartitionType), @@ -66,7 +64,7 @@ func Partition(pt *gpt.GPT, pos int, device string, partitionOpts Options) (stri return "", err } - log.Printf("created %s (%s) size %d blocks", partitionName, partitionOpts.PartitionLabel, part.Length()) + printf("created %s (%s) size %d blocks", partitionName, partitionOpts.PartitionLabel, part.Length()) return partitionName, nil } diff --git a/internal/pkg/secureboot/uki/sbat.go b/internal/pkg/secureboot/uki/sbat.go index e526f8b2c..d4331ad3d 100644 --- a/internal/pkg/secureboot/uki/sbat.go +++ b/internal/pkg/secureboot/uki/sbat.go @@ -7,7 +7,6 @@ package uki import ( "debug/pe" "fmt" - "log" "github.com/siderolabs/talos/internal/pkg/secureboot" ) @@ -23,8 +22,6 @@ func GetSBAT(path string) ([]byte, error) { for _, section := range pefile.Sections { if section.Name == string(secureboot.SBAT) { - log.Printf("section size: %d", section.Size) - data, err := section.Data() if err != nil { return nil, err diff --git a/internal/pkg/secureboot/uki/uki.go b/internal/pkg/secureboot/uki/uki.go index 6643085af..855e7e7ca 100644 --- a/internal/pkg/secureboot/uki/uki.go +++ b/internal/pkg/secureboot/uki/uki.go @@ -75,7 +75,7 @@ type Builder struct { // - build ephemeral sections (uname, os-release), and other proposed sections // - measure sections, generate signature, and append to the list of sections // - assemble the final UKI file starting from sd-stub and appending generated section. -func (builder *Builder) Build() error { +func (builder *Builder) Build(printf func(string, ...any)) error { var err error builder.scratchDir, err = os.MkdirTemp("", "talos-uki") @@ -89,6 +89,8 @@ func (builder *Builder) Build() error { } }() + printf("signing systemd-boot") + builder.peSigner, err = pesign.NewSigner(builder.SigningCertPath, builder.SigningKeyPath) if err != nil { return fmt.Errorf("error initilazing signer: %w", err) @@ -99,6 +101,8 @@ func (builder *Builder) Build() error { return fmt.Errorf("error signing sd-boot: %w", err) } + printf("generating UKI sections") + // generate and build list of all sections for _, generateSection := range []func() error{ builder.generateOSRel, @@ -118,11 +122,15 @@ func (builder *Builder) Build() error { } } + printf("assembling UKI") + // assemble the final UKI file if err = builder.assemble(); err != nil { return fmt.Errorf("error assembling UKI: %w", err) } + printf("signing UKI") + // sign the UKI file return builder.peSigner.Sign(builder.unsignedUKIPath, builder.OutUKIPath) } diff --git a/pkg/imager/extensions/rebuild.go b/pkg/imager/extensions/rebuild.go index 5b8c2c585..8ded71a29 100644 --- a/pkg/imager/extensions/rebuild.go +++ b/pkg/imager/extensions/rebuild.go @@ -29,7 +29,7 @@ func (builder *Builder) rebuildInitramfs(tempDir string) error { defer pipeW.Close() //nolint:errcheck // build cpio image which contains .sqsh images and extensions.yaml - cmd1 := exec.Command("cpio", "-H", "newc", "--create", "--reproducible") + cmd1 := exec.Command("cpio", "-H", "newc", "--create", "--reproducible", "--quiet") cmd1.Dir = tempDir cmd1.Stdin = listing cmd1.Stdout = pipeW @@ -51,7 +51,7 @@ func (builder *Builder) rebuildInitramfs(tempDir string) error { defer destination.Close() //nolint:errcheck // append compressed initramfs.sysext to the original initramfs.xz, kernel can read such format - cmd2 := exec.Command("xz", "-v", "-C", "crc32", "-0", "-e", "-T", "0", "-z") + cmd2 := exec.Command("xz", "-v", "-C", "crc32", "-0", "-e", "-T", "0", "-z", "--quiet") cmd2.Dir = tempDir cmd2.Stdin = pipeR cmd2.Stdout = destination diff --git a/pkg/imager/imager.go b/pkg/imager/imager.go index 01c00b74d..47a187d2d 100644 --- a/pkg/imager/imager.go +++ b/pkg/imager/imager.go @@ -8,7 +8,6 @@ package imager import ( "context" "fmt" - "log" "os" "path/filepath" "strconv" @@ -25,6 +24,7 @@ import ( "github.com/siderolabs/talos/pkg/machinery/config/merge" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/kernel" + "github.com/siderolabs/talos/pkg/reporter" "github.com/siderolabs/talos/pkg/version" ) @@ -80,7 +80,7 @@ func New(prof profile.Profile) (*Imager, error) { // Execute image generation. // //nolint:gocyclo,cyclop -func (i *Imager) Execute(ctx context.Context, outputPath string) error { +func (i *Imager) Execute(ctx context.Context, outputPath string, report *reporter.Reporter) error { var err error i.tempDir, err = os.MkdirTemp("", "imager") @@ -90,13 +90,18 @@ func (i *Imager) Execute(ctx context.Context, outputPath string) error { defer os.RemoveAll(i.tempDir) //nolint:errcheck + report.Report(reporter.Update{ + Message: "profile ready:", + Status: reporter.StatusSucceeded, + }) + // 0. Dump the profile. if err = i.prof.Dump(os.Stderr); err != nil { return err } // 1. Transform `initramfs.xz` with system extensions - if err = i.buildInitramfs(ctx); err != nil { + if err = i.buildInitramfs(ctx, report); err != nil { return err } @@ -105,11 +110,14 @@ func (i *Imager) Execute(ctx context.Context, outputPath string) error { return err } - log.Printf("assembled kernel command line: %s", i.cmdline) + report.Report(reporter.Update{ + Message: fmt.Sprintf("kernel command line: %s", i.cmdline), + Status: reporter.StatusSucceeded, + }) // 3. Build UKI if Secure Boot is enabled. if i.prof.SecureBootEnabled() { - if err = i.buildUKI(); err != nil { + if err = i.buildUKI(report); err != nil { return err } } @@ -117,21 +125,19 @@ func (i *Imager) Execute(ctx context.Context, outputPath string) error { // 4. Build the output. outputAssetPath := filepath.Join(outputPath, i.prof.OutputPath()) - log.Printf("output path: %s", outputAssetPath) - switch i.prof.Output.Kind { case profile.OutKindISO: - err = i.outISO(outputAssetPath) + err = i.outISO(outputAssetPath, report) case profile.OutKindKernel: - err = i.outKernel(outputAssetPath) + err = i.outKernel(outputAssetPath, report) case profile.OutKindUKI: - err = i.outUKI(outputAssetPath) + err = i.outUKI(outputAssetPath, report) case profile.OutKindInitramfs: - err = i.outInitramfs(outputAssetPath) + err = i.outInitramfs(outputAssetPath, report) case profile.OutKindImage: - err = i.outImage(ctx, outputAssetPath) + err = i.outImage(ctx, outputAssetPath, report) case profile.OutKindInstaller: - err = i.outInstaller(ctx, outputAssetPath) + err = i.outInstaller(ctx, outputAssetPath, report) case profile.OutKindUnknown: fallthrough default: @@ -142,17 +148,22 @@ func (i *Imager) Execute(ctx context.Context, outputPath string) error { return err } + report.Report(reporter.Update{ + Message: fmt.Sprintf("output asset path: %s", outputAssetPath), + Status: reporter.StatusSucceeded, + }) + // 5. Post-process the output. switch i.prof.Output.OutFormat { case profile.OutFormatRaw: // do nothing return nil case profile.OutFormatXZ: - return i.postProcessXz(outputAssetPath) + return i.postProcessXz(outputAssetPath, report) case profile.OutFormatGZ: - return i.postProcessGz(outputAssetPath) + return i.postProcessGz(outputAssetPath, report) case profile.OutFormatTar: - return i.postProcessTar(outputAssetPath) + return i.postProcessTar(outputAssetPath, report) case profile.OutFormatUnknown: fallthrough default: @@ -161,18 +172,25 @@ func (i *Imager) Execute(ctx context.Context, outputPath string) error { } // buildInitramfs transforms `initramfs.xz` with system extensions. -func (i *Imager) buildInitramfs(ctx context.Context) error { +func (i *Imager) buildInitramfs(ctx context.Context, report *reporter.Reporter) error { if len(i.prof.Input.SystemExtensions) == 0 { + report.Report(reporter.Update{ + Message: "skipped initramfs rebuild (no system extensions)", + Status: reporter.StatusSkip, + }) + // no system extensions, happy path i.initramfsPath = i.prof.Input.Initramfs.Path 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 tempInitramfsPath := filepath.Join(i.tempDir, "initramfs.xz") - if err := utils.CopyFiles(utils.SourceDestination(i.prof.Input.Initramfs.Path, tempInitramfsPath)); err != nil { + if err := utils.CopyFiles(printf, utils.SourceDestination(i.prof.Input.Initramfs.Path, tempInitramfsPath)); err != nil { return fmt.Errorf("failed to copy initramfs: %w", err) } @@ -188,7 +206,7 @@ func (i *Imager) buildInitramfs(ctx context.Context) error { return fmt.Errorf("failed to create extension directory: %w", err) } - if err := ext.Extract(ctx, extensionDir, i.prof.Arch); err != nil { + if err := ext.Extract(ctx, extensionDir, i.prof.Arch, printf); err != nil { return err } } @@ -198,10 +216,19 @@ func (i *Imager) buildInitramfs(ctx context.Context) error { InitramfsPath: i.initramfsPath, Arch: i.prof.Arch, ExtensionTreePath: extensionsCheckoutDir, - Printf: log.Printf, + Printf: printf, } - return builder.Build() + if err := builder.Build(); err != nil { + return err + } + + report.Report(reporter.Update{ + Message: "initramfs ready", + Status: reporter.StatusSucceeded, + }) + + return nil } // buildCmdline builds the kernel command line. @@ -262,7 +289,9 @@ func (i *Imager) buildCmdline() error { } // buildUKI assembles the UKI and signs it. -func (i *Imager) buildUKI() error { +func (i *Imager) buildUKI(report *reporter.Reporter) error { + printf := progressPrintf(report, reporter.Update{Message: "building UKI...", Status: reporter.StatusRunning}) + i.sdBootPath = filepath.Join(i.tempDir, "systemd-boot.efi.signed") i.ukiPath = filepath.Join(i.tempDir, "vmlinuz.efi.signed") @@ -283,5 +312,14 @@ func (i *Imager) buildUKI() error { OutUKIPath: i.ukiPath, } - return builder.Build() + if err := builder.Build(printf); err != nil { + return err + } + + report.Report(reporter.Update{ + Message: "UKI ready", + Status: reporter.StatusSucceeded, + }) + + return nil } diff --git a/pkg/imager/iso/grub.go b/pkg/imager/iso/grub.go index 2215dd1ad..3818811a1 100644 --- a/pkg/imager/iso/grub.go +++ b/pkg/imager/iso/grub.go @@ -8,7 +8,6 @@ import ( "bytes" _ "embed" "fmt" - "log" "os" "path/filepath" "text/template" @@ -37,15 +36,16 @@ var grubCfgTemplate string // CreateGRUB creates a GRUB-based ISO image. // // This iso supports both BIOS and UEFI booting. -func CreateGRUB(options GRUBOptions) error { +func CreateGRUB(printf func(string, ...any), options GRUBOptions) error { if err := utils.CopyFiles( + printf, utils.SourceDestination(options.KernelPath, filepath.Join(options.ScratchDir, "boot", "vmlinuz")), utils.SourceDestination(options.InitramfsPath, filepath.Join(options.ScratchDir, "boot", "initramfs.xz")), ); err != nil { return err } - log.Println("creating grub.cfg") + printf("creating grub.cfg") var grubCfg bytes.Buffer @@ -76,11 +76,11 @@ func CreateGRUB(options GRUBOptions) error { return err } - if err = utils.TouchFiles(options.ScratchDir); err != nil { + if err = utils.TouchFiles(printf, options.ScratchDir); err != nil { return err } - log.Println("creating ISO") + printf("creating ISO image") return grubMkrescue(options.OutPath, options.ScratchDir) } diff --git a/pkg/imager/iso/uefi.go b/pkg/imager/iso/uefi.go index c2a7de9fb..23c30621d 100644 --- a/pkg/imager/iso/uefi.go +++ b/pkg/imager/iso/uefi.go @@ -47,7 +47,7 @@ const ( // The ISO created supports only booting in UEFI mode, and supports SecureBoot. // //nolint:gocyclo,cyclop -func CreateUEFI(options UEFIOptions) error { +func CreateUEFI(printf func(string, ...any), options UEFIOptions) error { if err := os.MkdirAll(options.ScratchDir, 0o755); err != nil { return err } @@ -60,10 +60,12 @@ func CreateUEFI(options UEFIOptions) error { isoSize = UKIISOSizeARM64 } - if err := utils.CreateRawDisk(efiBootImg, isoSize); err != nil { + if err := utils.CreateRawDisk(printf, efiBootImg, isoSize); err != nil { return err } + printf("creating vFAT EFI image") + fopts := []makefs.Option{ makefs.WithLabel(constants.EFIPartitionLabel), makefs.WithReproducible(true), @@ -130,10 +132,12 @@ func CreateUEFI(options UEFIOptions) error { } // fixup directory timestamps recursively - if err := utils.TouchFiles(options.ScratchDir); err != nil { + if err := utils.TouchFiles(printf, options.ScratchDir); err != nil { return err } + printf("creating ISO image") + if _, err := cmd.Run( "xorriso", "-as", diff --git a/pkg/imager/out.go b/pkg/imager/out.go index e22ea5dbc..dbce75fad 100644 --- a/pkg/imager/out.go +++ b/pkg/imager/out.go @@ -28,25 +28,54 @@ import ( "github.com/siderolabs/talos/pkg/imager/qemuimg" "github.com/siderolabs/talos/pkg/imager/utils" "github.com/siderolabs/talos/pkg/machinery/constants" + "github.com/siderolabs/talos/pkg/reporter" ) -func (i *Imager) outInitramfs(path string) error { - return utils.CopyFiles(utils.SourceDestination(i.initramfsPath, path)) +func (i *Imager) outInitramfs(path string, report *reporter.Reporter) error { + printf := progressPrintf(report, reporter.Update{Message: "copying initramfs...", Status: reporter.StatusRunning}) + + if err := utils.CopyFiles(printf, utils.SourceDestination(i.initramfsPath, path)); err != nil { + return err + } + + report.Report(reporter.Update{Message: "initramfs output ready", Status: reporter.StatusSucceeded}) + + return nil } -func (i *Imager) outKernel(path string) error { - return utils.CopyFiles(utils.SourceDestination(i.prof.Input.Kernel.Path, path)) +func (i *Imager) outKernel(path string, report *reporter.Reporter) error { + printf := progressPrintf(report, reporter.Update{Message: "copying kernel...", Status: reporter.StatusRunning}) + + if err := utils.CopyFiles(printf, utils.SourceDestination(i.prof.Input.Kernel.Path, path)); err != nil { + return err + } + + report.Report(reporter.Update{Message: "kernel output ready", Status: reporter.StatusSucceeded}) + + return nil } -func (i *Imager) outUKI(path string) error { - return utils.CopyFiles(utils.SourceDestination(i.ukiPath, path)) +func (i *Imager) outUKI(path string, report *reporter.Reporter) error { + printf := progressPrintf(report, reporter.Update{Message: "copying kernel...", Status: reporter.StatusRunning}) + + if err := utils.CopyFiles(printf, utils.SourceDestination(i.ukiPath, path)); err != nil { + return err + } + + report.Report(reporter.Update{Message: "UKI output ready", Status: reporter.StatusSucceeded}) + + return nil } -func (i *Imager) outISO(path string) error { +func (i *Imager) outISO(path string, report *reporter.Reporter) error { + printf := progressPrintf(report, reporter.Update{Message: "building ISO...", Status: reporter.StatusRunning}) + scratchSpace := filepath.Join(i.tempDir, "iso") + var err error + if i.prof.SecureBootEnabled() { - return iso.CreateUEFI(iso.UEFIOptions{ + err = iso.CreateUEFI(printf, iso.UEFIOptions{ UKIPath: i.ukiPath, SDBootPath: i.sdBootPath, @@ -57,50 +86,70 @@ func (i *Imager) outISO(path string) error { Arch: i.prof.Arch, Version: i.prof.Version, + ScratchDir: scratchSpace, + OutPath: path, + }) + } else { + err = iso.CreateGRUB(printf, iso.GRUBOptions{ + KernelPath: i.prof.Input.Kernel.Path, + InitramfsPath: i.initramfsPath, + Cmdline: i.cmdline, + ScratchDir: scratchSpace, OutPath: path, }) } - return iso.CreateGRUB(iso.GRUBOptions{ - KernelPath: i.prof.Input.Kernel.Path, - InitramfsPath: i.initramfsPath, - Cmdline: i.cmdline, + if err != nil { + return err + } - ScratchDir: scratchSpace, - OutPath: path, - }) + report.Report(reporter.Update{Message: "ISO ready", Status: reporter.StatusSucceeded}) + + return nil } -func (i *Imager) outImage(ctx context.Context, path string) error { - if err := i.buildImage(ctx, path); err != nil { +func (i *Imager) outImage(ctx context.Context, path string, report *reporter.Reporter) error { + printf := progressPrintf(report, reporter.Update{Message: "creating disk image...", Status: reporter.StatusRunning}) + + if err := i.buildImage(ctx, path, printf); err != nil { return err } switch i.prof.Output.ImageOptions.DiskFormat { case profile.DiskFormatRaw: - return nil + // nothing to do case profile.DiskFormatQCOW2: - return qemuimg.Convert("raw", "qcow2", i.prof.Output.ImageOptions.DiskFormatOptions, path) + if err := qemuimg.Convert("raw", "qcow2", i.prof.Output.ImageOptions.DiskFormatOptions, path, printf); err != nil { + return err + } case profile.DiskFormatVPC: - return qemuimg.Convert("raw", "vpc", i.prof.Output.ImageOptions.DiskFormatOptions, path) + if err := qemuimg.Convert("raw", "vpc", i.prof.Output.ImageOptions.DiskFormatOptions, path, printf); err != nil { + return err + } case profile.DiskFormatOVA: scratchPath := filepath.Join(i.tempDir, "ova") - return ova.CreateOVAFromRAW(fmt.Sprintf("%s-%s", i.prof.Platform, i.prof.Arch), path, i.prof.Arch, scratchPath, i.prof.Output.ImageOptions.DiskSize) + if err := ova.CreateOVAFromRAW(fmt.Sprintf("%s-%s", i.prof.Platform, i.prof.Arch), path, i.prof.Arch, scratchPath, i.prof.Output.ImageOptions.DiskSize, printf); err != nil { + return err + } case profile.DiskFormatUnknown: fallthrough default: return fmt.Errorf("unsupported disk format: %s", i.prof.Output.ImageOptions.DiskFormat) } + + report.Report(reporter.Update{Message: "disk image ready", Status: reporter.StatusSucceeded}) + + return nil } -func (i *Imager) buildImage(ctx context.Context, path string) error { - if err := utils.CreateRawDisk(path, i.prof.Output.ImageOptions.DiskSize); err != nil { +func (i *Imager) buildImage(ctx context.Context, path string, printf func(string, ...any)) error { + if err := utils.CreateRawDisk(printf, path, i.prof.Output.ImageOptions.DiskSize); err != nil { return err } - log.Print("attaching loopback device") + printf("attaching loopback device") var ( loDevice string @@ -112,7 +161,7 @@ func (i *Imager) buildImage(ctx context.Context, path string) error { } defer func() { - log.Println("detaching loopback device") + printf("detaching loopback device") if e := utils.Lodetach(loDevice); e != nil { log.Println(e) @@ -136,6 +185,7 @@ func (i *Imager) buildImage(ctx context.Context, path string) error { UKIPath: i.ukiPath, SDBootPath: i.sdBootPath, }, + Printf: printf, } if opts.Board == "" { @@ -144,15 +194,21 @@ func (i *Imager) buildImage(ctx context.Context, path string) error { installer, err := install.NewInstaller(ctx, cmdline, install.ModeImage, opts) if err != nil { - return err + return fmt.Errorf("failed to create installer: %w", err) } - return installer.Install(ctx, install.ModeImage) + if err := installer.Install(ctx, install.ModeImage); err != nil { + return fmt.Errorf("failed to install: %w", err) + } + + return nil } //nolint:gocyclo -func (i *Imager) outInstaller(ctx context.Context, path string) error { - baseInstallerImg, err := i.prof.Input.BaseInstaller.Pull(ctx, i.prof.Arch) +func (i *Imager) outInstaller(ctx context.Context, path string, report *reporter.Reporter) error { + printf := progressPrintf(report, reporter.Update{Message: "building installer...", Status: reporter.StatusRunning}) + + baseInstallerImg, err := i.prof.Input.BaseInstaller.Pull(ctx, i.prof.Arch, printf) if err != nil { return err } @@ -169,6 +225,8 @@ func (i *Imager) outInstaller(ctx context.Context, path string) error { config := *configFile.Config.DeepCopy() + printf("creating empty image") + newInstallerImg := mutate.MediaType(empty.Image, types.OCIManifestSchema1) newInstallerImg = mutate.ConfigMediaType(newInstallerImg, types.OCIConfigJSON) @@ -189,6 +247,8 @@ func (i *Imager) outInstaller(ctx context.Context, path string) error { var artifacts []filemap.File + printf("generating artifacts layer") + if i.prof.SecureBootEnabled() { artifacts = append(artifacts, filemap.File{ @@ -228,5 +288,13 @@ func (i *Imager) outInstaller(ctx context.Context, path string) error { return fmt.Errorf("failed to parse image reference: %w", err) } - return tarball.WriteToFile(path, ref, newInstallerImg) + printf("writing image tarball") + + if err := tarball.WriteToFile(path, ref, newInstallerImg); err != nil { + return fmt.Errorf("failed to write image tarball: %w", err) + } + + report.Report(reporter.Update{Message: "installer container image ready", Status: reporter.StatusSucceeded}) + + return nil } diff --git a/pkg/imager/ova/ova.go b/pkg/imager/ova/ova.go index f2e4e21c2..a619671fd 100644 --- a/pkg/imager/ova/ova.go +++ b/pkg/imager/ova/ova.go @@ -141,18 +141,18 @@ const ovfTpl = ` // CreateOVAFromRAW creates an OVA from a RAW disk. // //nolint:gocyclo -func CreateOVAFromRAW(name, path, arch, scratchPath string, diskSize int64) error { +func CreateOVAFromRAW(name, path, arch, scratchPath string, diskSize int64, printf func(string, ...any)) error { if err := os.MkdirAll(scratchPath, 0o755); err != nil { return err } vmdkPath := filepath.Join(scratchPath, name+".vmdk") - if err := utils.CopyFiles(utils.SourceDestination(path, vmdkPath)); err != nil { + if err := utils.CopyFiles(printf, utils.SourceDestination(path, vmdkPath)); err != nil { return err } - if err := qemuimg.Convert("raw", "vmdk", "compat6,subformat=streamOptimized,adapter_type=lsilogic", vmdkPath); err != nil { + if err := qemuimg.Convert("raw", "vmdk", "compat6,subformat=streamOptimized,adapter_type=lsilogic", vmdkPath, printf); err != nil { return err } diff --git a/pkg/imager/post.go b/pkg/imager/post.go index 28a71bab5..58214a19b 100644 --- a/pkg/imager/post.go +++ b/pkg/imager/post.go @@ -5,13 +5,18 @@ package imager import ( + "fmt" "os" "path/filepath" "github.com/siderolabs/go-cmd/pkg/cmd" + + "github.com/siderolabs/talos/pkg/reporter" ) -func (i *Imager) postProcessTar(filename string) error { +func (i *Imager) postProcessTar(filename string, report *reporter.Reporter) error { + report.Report(reporter.Update{Message: "processing .tar.gz", Status: reporter.StatusRunning}) + dir := filepath.Dir(filename) src := "disk.raw" @@ -25,21 +30,35 @@ func (i *Imager) postProcessTar(filename string) error { return err } - return os.Remove(filepath.Join(dir, src)) -} - -func (i *Imager) postProcessGz(filename string) error { - if _, err := cmd.Run("pigz", "-6", filename); err != nil { + if err := os.Remove(filepath.Join(dir, src)); err != nil { return err } + report.Report(reporter.Update{Message: fmt.Sprintf("archive is ready: %s", outPath), Status: reporter.StatusSucceeded}) + return nil } -func (i *Imager) postProcessXz(filename string) error { - if _, err := cmd.Run("xz", "-0", "-T", "0", filename); err != nil { +func (i *Imager) postProcessGz(filename string, report *reporter.Reporter) error { + report.Report(reporter.Update{Message: "compressing .gz", Status: reporter.StatusRunning}) + + if _, err := cmd.Run("pigz", "-6", "-f", filename); err != nil { return err } + report.Report(reporter.Update{Message: fmt.Sprintf("compression done: %s.gz", filename), Status: reporter.StatusSucceeded}) + + return nil +} + +func (i *Imager) postProcessXz(filename string, report *reporter.Reporter) 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 + } + + report.Report(reporter.Update{Message: fmt.Sprintf("compression done: %s.xz", filename), Status: reporter.StatusSucceeded}) + return nil } diff --git a/pkg/imager/profile/input.go b/pkg/imager/profile/input.go index cefca1398..d189ca952 100644 --- a/pkg/imager/profile/input.go +++ b/pkg/imager/profile/input.go @@ -8,7 +8,6 @@ import ( "context" "fmt" "io" - "log" "os" "path/filepath" @@ -144,8 +143,8 @@ func fileExists(path string) bool { } // Pull the container asset to the path. -func (c *ContainerAsset) Pull(ctx context.Context, arch string) (v1.Image, error) { - log.Printf("pulling %s...", c.ImageRef) +func (c *ContainerAsset) Pull(ctx context.Context, arch string, printf func(string, ...any)) (v1.Image, error) { + printf("pulling %s...", c.ImageRef) img, err := crane.Pull(c.ImageRef, crane.WithPlatform(&v1.Platform{ Architecture: arch, @@ -159,8 +158,8 @@ func (c *ContainerAsset) Pull(ctx context.Context, arch string) (v1.Image, error } // Extract the container asset to the path. -func (c *ContainerAsset) Extract(ctx context.Context, destination, arch string) error { - img, err := c.Pull(ctx, arch) +func (c *ContainerAsset) Extract(ctx context.Context, destination, arch string, printf func(string, ...any)) error { + img, err := c.Pull(ctx, arch, printf) if err != nil { return err } diff --git a/pkg/imager/profile/profile.go b/pkg/imager/profile/profile.go index dbcc4bf2d..9817f52dd 100644 --- a/pkg/imager/profile/profile.go +++ b/pkg/imager/profile/profile.go @@ -102,7 +102,7 @@ func (p *Profile) Validate() error { return fmt.Errorf("customization of meta partition is not supported for %s output", p.Output.Kind) } case OutKindUKI: - if p.SecureBootEnabled() { + if !p.SecureBootEnabled() { return fmt.Errorf("!secureboot is not supported for %s output", p.Output.Kind) } } diff --git a/pkg/imager/progress.go b/pkg/imager/progress.go new file mode 100644 index 000000000..a67aac442 --- /dev/null +++ b/pkg/imager/progress.go @@ -0,0 +1,28 @@ +// 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 imager + +import ( + "fmt" + + "github.com/siderolabs/talos/pkg/reporter" +) + +// progressPrintf wraps a reporter.Reporter to report progress via Printf logging. +func progressPrintf(report *reporter.Reporter, status reporter.Update) func(format string, args ...any) { + return func(format string, args ...any) { + msg := status.Message + extra := fmt.Sprintf(format, args...) + + if extra != "" { + msg += "\n\t" + extra + } + + report.Report(reporter.Update{ + Message: msg, + Status: status.Status, + }) + } +} diff --git a/pkg/imager/qemuimg/qemuimg.go b/pkg/imager/qemuimg/qemuimg.go index 1f2adcb0c..1de7f8859 100644 --- a/pkg/imager/qemuimg/qemuimg.go +++ b/pkg/imager/qemuimg/qemuimg.go @@ -12,10 +12,12 @@ import ( ) // Convert converts an image from one format to another. -func Convert(inputFmt, outputFmt, options, path string) error { +func Convert(inputFmt, outputFmt, options, path string, printf func(string, ...any)) error { src := path + ".in" dest := path + printf("converting %s to %s", inputFmt, outputFmt) + if err := os.Rename(path, src); err != nil { return err } diff --git a/pkg/imager/utils/copy.go b/pkg/imager/utils/copy.go index a354d93b0..aacf4704c 100644 --- a/pkg/imager/utils/copy.go +++ b/pkg/imager/utils/copy.go @@ -7,7 +7,6 @@ package utils import ( "fmt" "io" - "log" "os" "path/filepath" @@ -23,7 +22,7 @@ func SourceDestination(src, dest string) CopyInstruction { } // CopyFiles copies files according to the given instructions. -func CopyFiles(instructions ...CopyInstruction) error { +func CopyFiles(printf func(string, ...any), instructions ...CopyInstruction) error { for _, instruction := range instructions { if err := func(instruction CopyInstruction) error { src, dest := instruction.F1, instruction.F2 @@ -32,7 +31,7 @@ func CopyFiles(instructions ...CopyInstruction) error { return err } - log.Printf("copying %s to %s", src, dest) + printf("copying %s to %s", src, dest) from, err := os.Open(src) if err != nil { @@ -52,7 +51,7 @@ func CopyFiles(instructions ...CopyInstruction) error { return err }(instruction); err != nil { - return fmt.Errorf("error copying %s -> %s", instruction.F1, instruction.F2) + return fmt.Errorf("error copying %s -> %s: %w", instruction.F1, instruction.F2, err) } } diff --git a/pkg/imager/utils/raw.go b/pkg/imager/utils/raw.go index 7c876e40d..f0351d475 100644 --- a/pkg/imager/utils/raw.go +++ b/pkg/imager/utils/raw.go @@ -6,7 +6,6 @@ package utils import ( "fmt" - "log" "os" "syscall" @@ -14,8 +13,8 @@ import ( ) // CreateRawDisk creates a raw disk image of the specified size. -func CreateRawDisk(path string, diskSize int64) error { - log.Printf("creating raw disk of size %s", humanize.Bytes(uint64(diskSize))) +func CreateRawDisk(printf func(string, ...any), path string, diskSize int64) error { + printf("creating raw disk of size %s", humanize.Bytes(uint64(diskSize))) f, err := os.Create(path) if err != nil { diff --git a/pkg/imager/utils/touch.go b/pkg/imager/utils/touch.go index a19855c80..4fcf23133 100644 --- a/pkg/imager/utils/touch.go +++ b/pkg/imager/utils/touch.go @@ -6,14 +6,13 @@ package utils import ( "io/fs" - "log" "os" "path/filepath" "time" ) // TouchFiles updates mtime for all the files under root if SOURCE_DATE_EPOCH is set. -func TouchFiles(root string) error { +func TouchFiles(printf func(string, ...any), root string) error { epochInt, ok, err := SourceDateEpoch() if err != nil { return err @@ -25,7 +24,7 @@ func TouchFiles(root string) error { timestamp := time.Unix(epochInt, 0) - log.Printf("changing timestamps under %q to %s", root, timestamp) + printf("changing timestamps under %q to %s", root, timestamp) return filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if err != nil {