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 | 31 ++++- 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, 353 insertions(+), 169 deletions(-) create mode 100644 pkg/imager/progress.go diff --git a/Makefile b/Makefile index 2fba5a869d..cfab41b5f7 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 44cb2db600..dc6cb117a9 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 6a158dafc8..580fb892a9 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 27a00f0e9d..fd226ad4af 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 9338046eb8..4a2d53fd1e 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 a3a05b6951..63aefeea23 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 4d5170a9ea..9288fb06ef 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 f8f62abd3a..73af2a4423 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 89f9c2dc87..8c7dd1caaa 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 d9343592ab..7b04eca629 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 0a84b7cbd9..0fceacb674 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 8026d7c0df..d327e7d036 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 30a147b37e..5f9cb2fe59 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 6b4c8700ac..fb15b18777 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 36fdce2491..3c498f5f5b 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 a94e3f2bb0..d3e7636181 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 d17ba1b1c5..79ffe22f1f 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 6b0ccd9030..fcafa687a9 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 e526f8b2ca..d4331ad3d2 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 6643085aff..855e7e7ca1 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 5b8c2c5859..8ded71a296 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 01c00b74dc..47a187d2d1 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, + } + + if err := builder.Build(); err != nil { + return err } - return builder.Build() + 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 2215dd1ad1..3818811a15 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 c2a7de9fbc..23c30621de 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 e22ea5dbcc..dbce75fad3 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, @@ -60,47 +89,67 @@ func (i *Imager) outISO(path string) error { 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, + }) + } + + if err != nil { + return err } - return iso.CreateGRUB(iso.GRUBOptions{ - KernelPath: i.prof.Input.Kernel.Path, - InitramfsPath: i.initramfsPath, - Cmdline: i.cmdline, + report.Report(reporter.Update{Message: "ISO ready", Status: reporter.StatusSucceeded}) - ScratchDir: scratchSpace, - OutPath: path, - }) + 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) + } + + if err := installer.Install(ctx, install.ModeImage); err != nil { + return fmt.Errorf("failed to install: %w", err) } - return installer.Install(ctx, install.ModeImage) + 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 f2e4e21c27..a619671fdc 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 28a71bab55..58214a19bf 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)) + 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) postProcessGz(filename string) error { - if _, err := cmd.Run("pigz", "-6", 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) error { - if _, err := cmd.Run("xz", "-0", "-T", "0", filename); err != 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 cefca13981..d189ca952b 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 dbcc4bf2df..9817f52dd6 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 0000000000..a67aac4423 --- /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 1f2adcb0c5..1de7f88597 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 a354d93b0e..aacf4704c5 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 7c876e40d2..f0351d475f 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 a19855c80e..4fcf23133d 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 {