From cd77c4c47d1e35deaefbb3e5f2778aafe2a6a9c3 Mon Sep 17 00:00:00 2001 From: Thomas Hipp Date: Wed, 17 Nov 2021 15:41:21 +0100 Subject: [PATCH 1/5] main: Handle interrupt and timeout with context Signed-off-by: Thomas Hipp --- distrobuilder/main.go | 72 ++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/distrobuilder/main.go b/distrobuilder/main.go index 5d084333..ef6aa9d0 100644 --- a/distrobuilder/main.go +++ b/distrobuilder/main.go @@ -54,6 +54,7 @@ import "C" import ( "bufio" "bytes" + "context" "errors" "fmt" "io" @@ -110,6 +111,8 @@ type cmdGlobal struct { interrupt chan os.Signal logger *zap.SugaredLogger overlayCleanup func() + ctx context.Context + cancel context.CancelFunc } func main() { @@ -126,25 +129,34 @@ func main() { os.Exit(1) } - // Timeout handler - go func() { - // No timeout set - if globalCmd.flagTimeout == 0 { - return - } + var err error + + globalCmd.logger, err = shared.GetLogger(globalCmd.flagDebug) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to get logger: %s\n", err) + os.Exit(1) + } - time.Sleep(time.Duration(globalCmd.flagTimeout) * time.Second) + if globalCmd.flagTimeout == 0 { + globalCmd.ctx, globalCmd.cancel = context.WithCancel(context.Background()) + } else { + globalCmd.ctx, globalCmd.cancel = context.WithTimeout(context.Background(), time.Duration(globalCmd.flagTimeout)*time.Second) + } - // exit all chroots otherwise we cannot remove the cache directory - for _, exit := range shared.ActiveChroots { - if exit != nil { - exit() + go func() { + for { + select { + case <-globalCmd.interrupt: + globalCmd.cancel() + globalCmd.logger.Info("Interrupted") + return + case <-globalCmd.ctx.Done(): + if globalCmd.flagTimeout > 0 { + globalCmd.logger.Info("Timed out") + } + return } } - - globalCmd.postRun(nil, nil) - fmt.Println("Timed out") - os.Exit(1) }() // Create temp directory if the cache directory isn't explicitly set @@ -157,14 +169,6 @@ func main() { globalCmd.flagCacheDir = dir } - - var err error - - globalCmd.logger, err = shared.GetLogger(globalCmd.flagDebug) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to get logger: %s\n", err) - os.Exit(1) - } }, PersistentPostRunE: globalCmd.postRun, CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true}, @@ -204,21 +208,6 @@ func main() { repackWindowsCmd := cmdRepackWindows{global: &globalCmd} app.AddCommand(repackWindowsCmd.command()) - go func() { - <-globalCmd.interrupt - - // exit all chroots otherwise we cannot remove the cache directory - for _, exit := range shared.ActiveChroots { - if exit != nil { - exit() - } - } - - globalCmd.postRun(nil, nil) - fmt.Println("Interrupted") - os.Exit(1) - }() - globalCmd.interrupt = make(chan os.Signal, 1) signal.Notify(globalCmd.interrupt, os.Interrupt) @@ -426,6 +415,13 @@ func (c *cmdGlobal) postRun(cmd *cobra.Command, args []string) error { defer c.logger.Sync() } + // exit all chroots otherwise we cannot remove the cache directory + for _, exit := range shared.ActiveChroots { + if exit != nil { + exit() + } + } + // Clean up overlay if c.overlayCleanup != nil { if hasLogger { From ba5ed944959cc80b271b9ed31a995f2493e2f665 Mon Sep 17 00:00:00 2001 From: Thomas Hipp Date: Wed, 17 Nov 2021 16:08:01 +0100 Subject: [PATCH 2/5] *: Add context to RunCommand Signed-off-by: Thomas Hipp --- distrobuilder/main.go | 12 ++++++------ distrobuilder/main_lxc.go | 10 +++++----- distrobuilder/main_lxd.go | 14 +++++++------- distrobuilder/vm.go | 28 +++++++++++++++------------- generators/cloud-init_test.go | 3 ++- generators/hostname_test.go | 5 +++-- generators/hosts_test.go | 5 +++-- generators/template_test.go | 5 +++-- image/lxc.go | 9 ++++++--- image/lxc_test.go | 5 +++-- image/lxd.go | 17 ++++++++++------- image/lxd_test.go | 3 ++- managers/common.go | 16 ++++++++++------ managers/equo.go | 4 ++-- managers/manager.go | 10 ++++++---- managers/pacman.go | 4 ++-- managers/zypper.go | 2 +- shared/util.go | 29 +++++++++++++++-------------- sources/almalinux-http.go | 4 ++-- sources/alpine-http.go | 4 ++-- sources/busybox.go | 2 +- sources/centos-http.go | 4 ++-- sources/common.go | 5 ++++- sources/debootstrap.go | 2 +- sources/oraclelinux-http.go | 8 ++++---- sources/plamolinux-http.go | 4 ++-- sources/rhel-common.go | 26 +++++++++++++------------- sources/rocky-http.go | 2 +- sources/source.go | 7 ++++--- sources/springdalelinux-http.go | 2 +- sources/ubuntu-http.go | 8 ++++---- 31 files changed, 142 insertions(+), 117 deletions(-) diff --git a/distrobuilder/main.go b/distrobuilder/main.go index ef6aa9d0..bd452ca7 100644 --- a/distrobuilder/main.go +++ b/distrobuilder/main.go @@ -285,7 +285,7 @@ func (c *cmdGlobal) preRunBuild(cmd *cobra.Command, args []string) error { } // Load and run downloader - downloader, err := sources.Load(c.definition.Source.Downloader, c.logger, *c.definition, c.sourceDir, c.flagCacheDir, c.flagSourcesDir) + downloader, err := sources.Load(c.ctx, c.definition.Source.Downloader, c.logger, *c.definition, c.sourceDir, c.flagCacheDir, c.flagSourcesDir) if err != nil { return fmt.Errorf("Failed to load downloader %q: %w", c.definition.Source.Downloader, err) } @@ -335,7 +335,7 @@ func (c *cmdGlobal) preRunBuild(cmd *cobra.Command, args []string) error { } } - manager, err := managers.Load(c.definition.Packages.Manager, c.logger, *c.definition) + manager, err := managers.Load(c.ctx, c.definition.Packages.Manager, c.logger, *c.definition) if err != nil { return fmt.Errorf("Failed to load manager %q: %w", c.definition.Packages.Manager, err) } @@ -351,7 +351,7 @@ func (c *cmdGlobal) preRunBuild(cmd *cobra.Command, args []string) error { // Run post unpack hook for _, hook := range c.definition.GetRunnableActions("post-unpack", imageTargets) { - err := shared.RunScript(hook.Action) + err := shared.RunScript(c.ctx, hook.Action) if err != nil { return fmt.Errorf("Failed to run post-unpack: %w", err) } @@ -369,7 +369,7 @@ func (c *cmdGlobal) preRunBuild(cmd *cobra.Command, args []string) error { // Run post packages hook for _, hook := range c.definition.GetRunnableActions("post-packages", imageTargets) { - err := shared.RunScript(hook.Action) + err := shared.RunScript(c.ctx, hook.Action) if err != nil { return fmt.Errorf("Failed to run post-packages: %w", err) } @@ -463,7 +463,7 @@ func (c *cmdGlobal) getOverlayDir() (string, func(), error) { overlayDir = filepath.Join(c.flagCacheDir, "overlay") // Use rsync if overlay doesn't work - err = shared.RsyncLocal(c.sourceDir+"/", overlayDir) + err = shared.RsyncLocal(c.ctx, c.sourceDir+"/", overlayDir) if err != nil { return "", nil, fmt.Errorf("Failed to copy image content: %w", err) } @@ -475,7 +475,7 @@ func (c *cmdGlobal) getOverlayDir() (string, func(), error) { overlayDir = filepath.Join(c.flagCacheDir, "overlay") // Use rsync if overlay doesn't work - err = shared.RsyncLocal(c.sourceDir+"/", overlayDir) + err = shared.RsyncLocal(c.ctx, c.sourceDir+"/", overlayDir) if err != nil { return "", nil, fmt.Errorf("Failed to copy image content: %w", err) } diff --git a/distrobuilder/main_lxc.go b/distrobuilder/main_lxc.go index 1ebe1481..5ca6402d 100644 --- a/distrobuilder/main_lxc.go +++ b/distrobuilder/main_lxc.go @@ -107,7 +107,7 @@ func (c *cmdLXC) runPack(cmd *cobra.Command, args []string, overlayDir string) e imageTargets := shared.ImageTargetAll | shared.ImageTargetContainer - manager, err := managers.Load(c.global.definition.Packages.Manager, c.global.logger, *c.global.definition) + manager, err := managers.Load(c.global.ctx, c.global.definition.Packages.Manager, c.global.logger, *c.global.definition) if err != nil { return fmt.Errorf("Failed to load manager %q: %w", c.global.definition.Packages.Manager, err) } @@ -123,7 +123,7 @@ func (c *cmdLXC) runPack(cmd *cobra.Command, args []string, overlayDir string) e // Run post unpack hook for _, hook := range c.global.definition.GetRunnableActions("post-unpack", imageTargets) { - err := shared.RunScript(hook.Action) + err := shared.RunScript(c.global.ctx, hook.Action) if err != nil { return fmt.Errorf("Failed to run post-unpack: %w", err) } @@ -141,7 +141,7 @@ func (c *cmdLXC) runPack(cmd *cobra.Command, args []string, overlayDir string) e // Run post packages hook for _, hook := range c.global.definition.GetRunnableActions("post-packages", imageTargets) { - err := shared.RunScript(hook.Action) + err := shared.RunScript(c.global.ctx, hook.Action) if err != nil { return fmt.Errorf("Failed to run post-packages: %w", err) } @@ -151,7 +151,7 @@ func (c *cmdLXC) runPack(cmd *cobra.Command, args []string, overlayDir string) e } func (c *cmdLXC) run(cmd *cobra.Command, args []string, overlayDir string) error { - img := image.NewLXCImage(overlayDir, c.global.targetDir, + img := image.NewLXCImage(c.global.ctx, overlayDir, c.global.targetDir, c.global.flagCacheDir, *c.global.definition) for _, file := range c.global.definition.Files { @@ -185,7 +185,7 @@ func (c *cmdLXC) run(cmd *cobra.Command, args []string, overlayDir string) error // Run post files hook for _, action := range c.global.definition.GetRunnableActions("post-files", shared.ImageTargetUndefined|shared.ImageTargetAll|shared.ImageTargetContainer) { - err := shared.RunScript(action.Action) + err := shared.RunScript(c.global.ctx, action.Action) if err != nil { exitChroot() return fmt.Errorf("Failed to run post-files: %w", err) diff --git a/distrobuilder/main_lxd.go b/distrobuilder/main_lxd.go index f9e49664..e0bd3256 100644 --- a/distrobuilder/main_lxd.go +++ b/distrobuilder/main_lxd.go @@ -167,7 +167,7 @@ func (c *cmdLXD) runPack(cmd *cobra.Command, args []string, overlayDir string) e imageTargets = shared.ImageTargetContainer } - manager, err := managers.Load(c.global.definition.Packages.Manager, c.global.logger, *c.global.definition) + manager, err := managers.Load(c.global.ctx, c.global.definition.Packages.Manager, c.global.logger, *c.global.definition) if err != nil { return fmt.Errorf("Failed to load manager %q: %w", c.global.definition.Packages.Manager, err) } @@ -183,7 +183,7 @@ func (c *cmdLXD) runPack(cmd *cobra.Command, args []string, overlayDir string) e // Run post unpack hook for _, hook := range c.global.definition.GetRunnableActions("post-unpack", imageTargets) { - err := shared.RunScript(hook.Action) + err := shared.RunScript(c.global.ctx, hook.Action) if err != nil { return fmt.Errorf("Failed to run post-unpack: %w", err) } @@ -201,7 +201,7 @@ func (c *cmdLXD) runPack(cmd *cobra.Command, args []string, overlayDir string) e // Run post packages hook for _, hook := range c.global.definition.GetRunnableActions("post-packages", imageTargets) { - err := shared.RunScript(hook.Action) + err := shared.RunScript(c.global.ctx, hook.Action) if err != nil { return fmt.Errorf("Failed to run post-packages: %w", err) } @@ -211,7 +211,7 @@ func (c *cmdLXD) runPack(cmd *cobra.Command, args []string, overlayDir string) e } func (c *cmdLXD) run(cmd *cobra.Command, args []string, overlayDir string) error { - img := image.NewLXDImage(overlayDir, c.global.targetDir, + img := image.NewLXDImage(c.global.ctx, overlayDir, c.global.targetDir, c.global.flagCacheDir, *c.global.definition) imageTargets := shared.ImageTargetUndefined | shared.ImageTargetAll @@ -260,7 +260,7 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string, overlayDir string) error imgFile := filepath.Join(c.global.flagCacheDir, imgFilename) - vm, err = newVM(imgFile, vmDir, c.global.definition.Targets.LXD.VM.Filesystem, c.global.definition.Targets.LXD.VM.Size) + vm, err = newVM(c.global.ctx, imgFile, vmDir, c.global.definition.Targets.LXD.VM.Filesystem, c.global.definition.Targets.LXD.VM.Size) if err != nil { return fmt.Errorf("Failed to instanciate VM: %w", err) } @@ -304,7 +304,7 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string, overlayDir string) error // We cannot use LXD's rsync package as that uses the --delete flag which // causes an issue due to the boot/efi directory being present. - err = shared.RsyncLocal(overlayDir+"/", vmDir) + err = shared.RsyncLocal(c.global.ctx, overlayDir+"/", vmDir) if err != nil { return fmt.Errorf("Failed to copy rootfs: %w", err) } @@ -350,7 +350,7 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string, overlayDir string) error // Run post files hook for _, action := range c.global.definition.GetRunnableActions("post-files", imageTargets) { - err := shared.RunScript(action.Action) + err := shared.RunScript(c.global.ctx, action.Action) if err != nil { exitChroot() return fmt.Errorf("Failed to run post-files: %w", err) diff --git a/distrobuilder/vm.go b/distrobuilder/vm.go index 7ed3a978..e855f3bc 100644 --- a/distrobuilder/vm.go +++ b/distrobuilder/vm.go @@ -1,6 +1,7 @@ package main import ( + "context" "errors" "fmt" "os" @@ -20,9 +21,10 @@ type vm struct { rootFS string rootfsDir string size uint64 + ctx context.Context } -func newVM(imageFile, rootfsDir, fs string, size uint64) (*vm, error) { +func newVM(ctx context.Context, imageFile, rootfsDir, fs string, size uint64) (*vm, error) { if fs == "" { fs = "ext4" } @@ -35,7 +37,7 @@ func newVM(imageFile, rootfsDir, fs string, size uint64) (*vm, error) { size = 4294967296 } - return &vm{imageFile: imageFile, rootfsDir: rootfsDir, rootFS: fs, size: size}, nil + return &vm{ctx: ctx, imageFile: imageFile, rootfsDir: rootfsDir, rootFS: fs, size: size}, nil } func (v *vm) getLoopDev() string { @@ -86,7 +88,7 @@ func (v *vm) createPartitions() error { } for _, cmd := range args { - err := shared.RunCommand("sgdisk", append([]string{v.imageFile}, cmd...)...) + err := shared.RunCommand(v.ctx, "sgdisk", append([]string{v.imageFile}, cmd...)...) if err != nil { return fmt.Errorf("Failed to create partitions: %w", err) } @@ -169,7 +171,7 @@ func (v *vm) umountImage() error { return nil } - err := shared.RunCommand("losetup", "-d", v.loopDevice) + err := shared.RunCommand(v.ctx, "losetup", "-d", v.loopDevice) if err != nil { return fmt.Errorf("Failed to detach loop device: %w", err) } @@ -201,21 +203,21 @@ func (v *vm) createRootFS() error { switch v.rootFS { case "btrfs": - err := shared.RunCommand("mkfs.btrfs", "-f", "-L", "rootfs", v.getRootfsDevFile()) + err := shared.RunCommand(v.ctx, "mkfs.btrfs", "-f", "-L", "rootfs", v.getRootfsDevFile()) if err != nil { return fmt.Errorf("Failed to create btrfs filesystem: %w", err) } // Create the root subvolume as well - err = shared.RunCommand("mount", v.getRootfsDevFile(), v.rootfsDir) + err = shared.RunCommand(v.ctx, "mount", v.getRootfsDevFile(), v.rootfsDir) if err != nil { return fmt.Errorf("Failed to mount %q at %q: %w", v.getRootfsDevFile(), v.rootfsDir, err) } - defer shared.RunCommand("umount", v.rootfsDir) + defer shared.RunCommand(v.ctx, "umount", v.rootfsDir) - return shared.RunCommand("btrfs", "subvolume", "create", fmt.Sprintf("%s/@", v.rootfsDir)) + return shared.RunCommand(v.ctx, "btrfs", "subvolume", "create", fmt.Sprintf("%s/@", v.rootfsDir)) case "ext4": - return shared.RunCommand("mkfs.ext4", "-F", "-b", "4096", "-i 8192", "-m", "0", "-L", "rootfs", "-E", "resize=536870912", v.getRootfsDevFile()) + return shared.RunCommand(v.ctx, "mkfs.ext4", "-F", "-b", "4096", "-i 8192", "-m", "0", "-L", "rootfs", "-E", "resize=536870912", v.getRootfsDevFile()) } return nil @@ -226,7 +228,7 @@ func (v *vm) createUEFIFS() error { return errors.New("Disk image not mounted") } - return shared.RunCommand("mkfs.vfat", "-F", "32", "-n", "UEFI", v.getUEFIDevFile()) + return shared.RunCommand(v.ctx, "mkfs.vfat", "-F", "32", "-n", "UEFI", v.getUEFIDevFile()) } func (v *vm) getRootfsPartitionUUID() (string, error) { @@ -262,9 +264,9 @@ func (v *vm) mountRootPartition() error { switch v.rootFS { case "btrfs": - return shared.RunCommand("mount", v.getRootfsDevFile(), v.rootfsDir, "-o", "defaults,subvol=/@") + return shared.RunCommand(v.ctx, "mount", v.getRootfsDevFile(), v.rootfsDir, "-o", "defaults,subvol=/@") case "ext4": - return shared.RunCommand("mount", v.getRootfsDevFile(), v.rootfsDir) + return shared.RunCommand(v.ctx, "mount", v.getRootfsDevFile(), v.rootfsDir) } @@ -283,5 +285,5 @@ func (v *vm) mountUEFIPartition() error { return fmt.Errorf("Failed to create directory %q: %w", mountpoint, err) } - return shared.RunCommand("mount", v.getUEFIDevFile(), mountpoint) + return shared.RunCommand(v.ctx, "mount", v.getUEFIDevFile(), mountpoint) } diff --git a/generators/cloud-init_test.go b/generators/cloud-init_test.go index 6846335d..6326929b 100644 --- a/generators/cloud-init_test.go +++ b/generators/cloud-init_test.go @@ -1,6 +1,7 @@ package generators import ( + "context" "fmt" "log" "os" @@ -88,7 +89,7 @@ func TestCloudInitGeneratorRunLXD(t *testing.T) { }, } - image := image.NewLXDImage(cacheDir, "", cacheDir, definition) + image := image.NewLXDImage(context.TODO(), cacheDir, "", cacheDir, definition) tests := []struct { name string diff --git a/generators/hostname_test.go b/generators/hostname_test.go index 94cbba35..e4192b03 100644 --- a/generators/hostname_test.go +++ b/generators/hostname_test.go @@ -1,6 +1,7 @@ package generators import ( + "context" "os" "path/filepath" "testing" @@ -29,7 +30,7 @@ func TestHostnameGeneratorRunLXC(t *testing.T) { }, } - image := image.NewLXCImage(cacheDir, "", cacheDir, definition) + image := image.NewLXCImage(context.TODO(), cacheDir, "", cacheDir, definition) err = os.MkdirAll(filepath.Join(cacheDir, "rootfs", "etc"), 0755) require.NoError(t, err) @@ -60,7 +61,7 @@ func TestHostnameGeneratorRunLXD(t *testing.T) { }, } - image := image.NewLXDImage(cacheDir, "", cacheDir, definition) + image := image.NewLXDImage(context.TODO(), cacheDir, "", cacheDir, definition) err = os.MkdirAll(filepath.Join(cacheDir, "rootfs", "etc"), 0755) require.NoError(t, err) diff --git a/generators/hosts_test.go b/generators/hosts_test.go index a2a968c5..992f2eff 100644 --- a/generators/hosts_test.go +++ b/generators/hosts_test.go @@ -1,6 +1,7 @@ package generators import ( + "context" "os" "path/filepath" "testing" @@ -29,7 +30,7 @@ func TestHostsGeneratorRunLXC(t *testing.T) { }, } - image := image.NewLXCImage(cacheDir, "", cacheDir, definition) + image := image.NewLXCImage(context.TODO(), cacheDir, "", cacheDir, definition) err = os.MkdirAll(filepath.Join(cacheDir, "rootfs", "etc"), 0755) require.NoError(t, err) @@ -62,7 +63,7 @@ func TestHostsGeneratorRunLXD(t *testing.T) { }, } - image := image.NewLXDImage(cacheDir, "", cacheDir, definition) + image := image.NewLXDImage(context.TODO(), cacheDir, "", cacheDir, definition) err = os.MkdirAll(filepath.Join(cacheDir, "rootfs", "etc"), 0755) require.NoError(t, err) diff --git a/generators/template_test.go b/generators/template_test.go index 09ffd347..587366f8 100644 --- a/generators/template_test.go +++ b/generators/template_test.go @@ -1,6 +1,7 @@ package generators import ( + "context" "os" "path/filepath" "testing" @@ -34,7 +35,7 @@ func TestTemplateGeneratorRunLXD(t *testing.T) { require.IsType(t, &template{}, generator) require.NoError(t, err) - image := image.NewLXDImage(cacheDir, "", cacheDir, definition) + image := image.NewLXDImage(context.TODO(), cacheDir, "", cacheDir, definition) err = os.MkdirAll(filepath.Join(cacheDir, "rootfs", "root"), 0755) require.NoError(t, err) @@ -71,7 +72,7 @@ func TestTemplateGeneratorRunLXDDefaultWhen(t *testing.T) { require.IsType(t, &template{}, generator) require.NoError(t, err) - image := image.NewLXDImage(cacheDir, "", cacheDir, definition) + image := image.NewLXDImage(context.TODO(), cacheDir, "", cacheDir, definition) err = generator.RunLXD(image, shared.DefinitionTargetLXD{}) require.NoError(t, err) diff --git a/image/lxc.go b/image/lxc.go index 551d5206..c8e43c5c 100644 --- a/image/lxc.go +++ b/image/lxc.go @@ -1,6 +1,7 @@ package image import ( + "context" "fmt" "os" "path/filepath" @@ -20,15 +21,17 @@ type LXCImage struct { targetDir string cacheDir string definition shared.Definition + ctx context.Context } // NewLXCImage returns a LXCImage. -func NewLXCImage(sourceDir, targetDir, cacheDir string, definition shared.Definition) *LXCImage { +func NewLXCImage(ctx context.Context, sourceDir, targetDir, cacheDir string, definition shared.Definition) *LXCImage { img := LXCImage{ sourceDir, targetDir, cacheDir, definition, + ctx, } // create metadata directory @@ -71,7 +74,7 @@ func (l *LXCImage) Build(compression string) error { return fmt.Errorf("Failed to pack metadata: %w", err) } - _, err = shared.Pack(filepath.Join(l.targetDir, "rootfs.tar"), compression, l.sourceDir, ".") + _, err = shared.Pack(l.ctx, filepath.Join(l.targetDir, "rootfs.tar"), compression, l.sourceDir, ".") if err != nil { return fmt.Errorf("Failed to pack %q: %w", filepath.Join(l.targetDir, "rootfs.tar"), err) } @@ -184,7 +187,7 @@ func (l *LXCImage) packMetadata() error { files = append(files, "templates") } - _, err = shared.Pack(filepath.Join(l.targetDir, "meta.tar"), "xz", + _, err = shared.Pack(l.ctx, filepath.Join(l.targetDir, "meta.tar"), "xz", filepath.Join(l.cacheDir, "metadata"), files...) if err != nil { return fmt.Errorf("Failed to create metadata: %w", err) diff --git a/image/lxc_test.go b/image/lxc_test.go index d1c9f06d..cf1b7ad4 100644 --- a/image/lxc_test.go +++ b/image/lxc_test.go @@ -2,6 +2,7 @@ package image import ( "bytes" + "context" "fmt" "io" "log" @@ -85,7 +86,7 @@ func lxcCacheDir() string { } func setupLXC() *LXCImage { - return NewLXCImage(lxcCacheDir(), "", lxcCacheDir(), lxcDef) + return NewLXCImage(context.TODO(), lxcCacheDir(), "", lxcCacheDir(), lxcDef) } func teardownLXC() { @@ -93,7 +94,7 @@ func teardownLXC() { } func TestNewLXCImage(t *testing.T) { - image := NewLXCImage(lxcCacheDir(), "", lxcCacheDir(), lxcDef) + image := NewLXCImage(context.TODO(), lxcCacheDir(), "", lxcCacheDir(), lxcDef) defer teardownLXC() require.Equal(t, lxcCacheDir(), image.cacheDir) diff --git a/image/lxd.go b/image/lxd.go index 49dc2848..7e3624af 100644 --- a/image/lxd.go +++ b/image/lxd.go @@ -1,6 +1,7 @@ package image import ( + "context" "fmt" "os" "path/filepath" @@ -19,10 +20,11 @@ type LXDImage struct { cacheDir string Metadata api.ImageMetadata definition shared.Definition + ctx context.Context } // NewLXDImage returns a LXDImage. -func NewLXDImage(sourceDir, targetDir, cacheDir string, +func NewLXDImage(ctx context.Context, sourceDir, targetDir, cacheDir string, definition shared.Definition) *LXDImage { return &LXDImage{ sourceDir, @@ -33,6 +35,7 @@ func NewLXDImage(sourceDir, targetDir, cacheDir string, Templates: make(map[string]*api.ImageMetadataTemplate), }, definition, + ctx, } } @@ -81,7 +84,7 @@ func (l *LXDImage) Build(unified bool, compression string, vm bool) (string, str if vm { // Create compressed qcow2 image. - err = shared.RunCommand("qemu-img", "convert", "-c", "-O", "qcow2", "-o", "compat=0.10", + err = shared.RunCommand(l.ctx, "qemu-img", "convert", "-c", "-O", "qcow2", "-o", "compat=0.10", rawImage, qcowImage) if err != nil { @@ -105,11 +108,11 @@ func (l *LXDImage) Build(unified bool, compression string, vm bool) (string, str return "", "", fmt.Errorf("Failed to rename image %q -> %q: %w", qcowImage, filepath.Join(filepath.Dir(qcowImage), "rootfs.img"), err) } - _, err = shared.Pack(targetTarball, "", l.cacheDir, "rootfs.img") + _, err = shared.Pack(l.ctx, targetTarball, "", l.cacheDir, "rootfs.img") } else { // Add the rootfs to the tarball, prefix all files with "rootfs". // We intentionally don't set any compression here, as PackUpdate (further down) cannot deal with compressed tarballs. - _, err = shared.Pack(targetTarball, + _, err = shared.Pack(l.ctx, targetTarball, "", l.sourceDir, "--transform", "s,^./,rootfs/,", ".") } if err != nil { @@ -122,7 +125,7 @@ func (l *LXDImage) Build(unified bool, compression string, vm bool) (string, str }() // Add the metadata to the tarball which is located in the cache directory - imageFile, err = shared.PackUpdate(targetTarball, compression, l.cacheDir, paths...) + imageFile, err = shared.PackUpdate(l.ctx, targetTarball, compression, l.cacheDir, paths...) if err != nil { return "", "", fmt.Errorf("Failed to add metadata to tarball %q: %w", targetTarball, err) } @@ -135,7 +138,7 @@ func (l *LXDImage) Build(unified bool, compression string, vm bool) (string, str rootfsFile = filepath.Join(l.targetDir, "rootfs.squashfs") // Create rootfs as squashfs. - err = shared.RunCommand("mksquashfs", l.sourceDir, + err = shared.RunCommand(l.ctx, "mksquashfs", l.sourceDir, rootfsFile, "-noappend", "-comp", compression, "-b", "1M", "-no-progress", "-no-recovery") } @@ -144,7 +147,7 @@ func (l *LXDImage) Build(unified bool, compression string, vm bool) (string, str } // Create metadata tarball. - imageFile, err = shared.Pack(filepath.Join(l.targetDir, "lxd.tar"), compression, + imageFile, err = shared.Pack(l.ctx, filepath.Join(l.targetDir, "lxd.tar"), compression, l.cacheDir, paths...) if err != nil { return "", "", fmt.Errorf("Failed to create metadata tarball: %w", err) diff --git a/image/lxd_test.go b/image/lxd_test.go index 4194fb4e..0cad3c7a 100644 --- a/image/lxd_test.go +++ b/image/lxd_test.go @@ -1,6 +1,7 @@ package image import ( + "context" "fmt" "log" "os" @@ -40,7 +41,7 @@ func setupLXD(t *testing.T) *LXDImage { err = os.MkdirAll(filepath.Join(cacheDir, "templates"), 0755) require.NoError(t, err) - image := NewLXDImage(cacheDir, "", cacheDir, lxdDef) + image := NewLXDImage(context.TODO(), cacheDir, "", cacheDir, lxdDef) fail := true defer func() { diff --git a/managers/common.go b/managers/common.go index 7dce1431..56908252 100644 --- a/managers/common.go +++ b/managers/common.go @@ -1,6 +1,8 @@ package managers import ( + "context" + "github.com/lxc/distrobuilder/shared" "go.uber.org/zap" ) @@ -11,11 +13,13 @@ type common struct { hooks managerHooks logger *zap.SugaredLogger definition shared.Definition + ctx context.Context } -func (c *common) init(logger *zap.SugaredLogger, definition shared.Definition) { +func (c *common) init(ctx context.Context, logger *zap.SugaredLogger, definition shared.Definition) { c.logger = logger c.definition = definition + c.ctx = ctx } // Install installs packages to the rootfs. @@ -28,7 +32,7 @@ func (c *common) install(pkgs, flags []string) error { args = append(args, flags...) args = append(args, pkgs...) - return shared.RunCommand(c.commands.install, args...) + return shared.RunCommand(c.ctx, c.commands.install, args...) } // Remove removes packages from the rootfs. @@ -41,7 +45,7 @@ func (c *common) remove(pkgs, flags []string) error { args = append(args, flags...) args = append(args, pkgs...) - return shared.RunCommand(c.commands.remove, args...) + return shared.RunCommand(c.ctx, c.commands.remove, args...) } // Clean cleans up cached files used by the package managers. @@ -54,7 +58,7 @@ func (c *common) clean() error { args := append(c.flags.global, c.flags.clean...) - err = shared.RunCommand(c.commands.clean, args...) + err = shared.RunCommand(c.ctx, c.commands.clean, args...) if err != nil { return err } @@ -81,7 +85,7 @@ func (c *common) refresh() error { args := append(c.flags.global, c.flags.refresh...) - return shared.RunCommand(c.commands.refresh, args...) + return shared.RunCommand(c.ctx, c.commands.refresh, args...) } // Update updates all packages. @@ -92,7 +96,7 @@ func (c *common) update() error { args := append(c.flags.global, c.flags.update...) - return shared.RunCommand(c.commands.update, args...) + return shared.RunCommand(c.ctx, c.commands.update, args...) } // SetInstallFlags overrides the default install flags. diff --git a/managers/equo.go b/managers/equo.go index e41e3c1a..bd669e23 100644 --- a/managers/equo.go +++ b/managers/equo.go @@ -66,7 +66,7 @@ func (m *equo) enmanRepoCaller(repo shared.DefinitionPackagesRepository) error { args = append(args, repo.Name) } - return shared.RunCommand("enman", args...) + return shared.RunCommand(m.ctx, "enman", args...) } func (m *equo) equoRepoCaller(repo shared.DefinitionPackagesRepository) error { @@ -78,6 +78,6 @@ func (m *equo) equoRepoCaller(repo shared.DefinitionPackagesRepository) error { return errors.New("Invalid repository url") } - return shared.RunCommand("equo", "repo", "add", "--repo", repo.URL, "--pkg", repo.URL, + return shared.RunCommand(m.ctx, "equo", "repo", "add", "--repo", repo.URL, "--pkg", repo.URL, repo.Name) } diff --git a/managers/manager.go b/managers/manager.go index a4cb65cd..0d41ed74 100644 --- a/managers/manager.go +++ b/managers/manager.go @@ -1,6 +1,7 @@ package managers import ( + "context" "errors" "fmt" "strings" @@ -42,10 +43,11 @@ type managerCommands struct { type Manager struct { mgr manager def shared.Definition + ctx context.Context } type manager interface { - init(logger *zap.SugaredLogger, definition shared.Definition) + init(ctx context.Context, logger *zap.SugaredLogger, definition shared.Definition) load() error manageRepository(repo shared.DefinitionPackagesRepository) error install(pkgs, flags []string) error @@ -72,7 +74,7 @@ var managers = map[string]func() manager{ } // Load loads and initializes a downloader. -func Load(managerName string, logger *zap.SugaredLogger, definition shared.Definition) (*Manager, error) { +func Load(ctx context.Context, managerName string, logger *zap.SugaredLogger, definition shared.Definition) (*Manager, error) { df, ok := managers[managerName] if !ok { return nil, ErrUnknownManager @@ -80,7 +82,7 @@ func Load(managerName string, logger *zap.SugaredLogger, definition shared.Defin d := df() - d.init(logger, definition) + d.init(ctx, logger, definition) err := d.load() if err != nil { @@ -121,7 +123,7 @@ func (m *Manager) ManagePackages(imageTarget shared.ImageTarget) error { // Run post update hook for _, action := range m.def.GetRunnableActions("post-update", imageTarget) { - err = shared.RunScript(action.Action) + err = shared.RunScript(m.ctx, action.Action) if err != nil { return fmt.Errorf("Failed to run post-update: %w", err) } diff --git a/managers/pacman.go b/managers/pacman.go index d2636f70..36d7cc2a 100644 --- a/managers/pacman.go +++ b/managers/pacman.go @@ -94,7 +94,7 @@ func (m *pacman) setupTrustedKeys() error { return nil } - err = shared.RunCommand("pacman-key", "--init") + err = shared.RunCommand(m.ctx, "pacman-key", "--init") if err != nil { return fmt.Errorf("Error initializing with pacman-key: %w", err) } @@ -107,7 +107,7 @@ func (m *pacman) setupTrustedKeys() error { keyring = "archlinux" } - err = shared.RunCommand("pacman-key", "--populate", keyring) + err = shared.RunCommand(m.ctx, "pacman-key", "--populate", keyring) if err != nil { return fmt.Errorf("Error populating with pacman-key: %w", err) } diff --git a/managers/zypper.go b/managers/zypper.go index 8c467eaa..2174dbd1 100644 --- a/managers/zypper.go +++ b/managers/zypper.go @@ -60,5 +60,5 @@ func (m *zypper) manageRepository(repoAction shared.DefinitionPackagesRepository return errors.New("Invalid repository url") } - return shared.RunCommand("zypper", "ar", "--refresh", "--check", repoAction.URL, repoAction.Name) + return shared.RunCommand(m.ctx, "zypper", "ar", "--refresh", "--check", repoAction.URL, repoAction.Name) } diff --git a/shared/util.go b/shared/util.go index 0d0086c1..1e1c3202 100644 --- a/shared/util.go +++ b/shared/util.go @@ -1,6 +1,7 @@ package shared import ( + "context" "fmt" "io" "os" @@ -51,8 +52,8 @@ func Copy(src, dest string) error { // RunCommand runs a command hereby setting the SHELL and PATH env variables, // and redirecting the process's stdout and stderr to the real stdout and stderr // respectively. -func RunCommand(name string, arg ...string) error { - cmd := exec.Command(name, arg...) +func RunCommand(ctx context.Context, name string, arg ...string) error { + cmd := exec.CommandContext(ctx, name, arg...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout @@ -64,7 +65,7 @@ func RunCommand(name string, arg ...string) error { // RunScript runs a script hereby setting the SHELL and PATH env variables, // and redirecting the process's stdout and stderr to the real stdout and stderr // respectively. -func RunScript(content string) error { +func RunScript(ctx context.Context, content string) error { fd, err := unix.MemfdCreate("tmp", 0) if err != nil { return fmt.Errorf("Failed to create memfd: %w", err) @@ -78,33 +79,33 @@ func RunScript(content string) error { fdPath := fmt.Sprintf("/proc/self/fd/%d", fd) - return RunCommand(fdPath) + return RunCommand(ctx, fdPath) } // Pack creates an uncompressed tarball. -func Pack(filename, compression, path string, args ...string) (string, error) { - err := RunCommand("tar", append([]string{"--xattrs", "-cf", filename, "-C", path}, args...)...) +func Pack(ctx context.Context, filename, compression, path string, args ...string) (string, error) { + err := RunCommand(ctx, "tar", append([]string{"--xattrs", "-cf", filename, "-C", path}, args...)...) if err != nil { // Clean up incomplete tarball os.Remove(filename) return "", fmt.Errorf("Failed to create tarball: %w", err) } - return compressTarball(filename, compression) + return compressTarball(ctx, filename, compression) } // PackUpdate updates an existing tarball. -func PackUpdate(filename, compression, path string, args ...string) (string, error) { - err := RunCommand("tar", append([]string{"--xattrs", "-uf", filename, "-C", path}, args...)...) +func PackUpdate(ctx context.Context, filename, compression, path string, args ...string) (string, error) { + err := RunCommand(ctx, "tar", append([]string{"--xattrs", "-uf", filename, "-C", path}, args...)...) if err != nil { return "", fmt.Errorf("Failed to update tarball: %w", err) } - return compressTarball(filename, compression) + return compressTarball(ctx, filename, compression) } // compressTarball compresses a tarball, or not. -func compressTarball(filename, compression string) (string, error) { +func compressTarball(ctx context.Context, filename, compression string) (string, error) { fileExtension := "" switch compression { @@ -113,7 +114,7 @@ func compressTarball(filename, compression string) (string, error) { defer os.Remove(filename) fallthrough case "bzip2", "xz", "lzip", "lzma", "gzip": - err := RunCommand(compression, "-f", filename) + err := RunCommand(ctx, compression, "-f", filename) if err != nil { return "", fmt.Errorf("Failed to compress tarball %q: %w", filename, err) } @@ -235,8 +236,8 @@ func SetEnvVariables(env Environment) Environment { } // RsyncLocal copies src to dest using rsync. -func RsyncLocal(src string, dest string) error { - err := RunCommand("rsync", "-aHASX", "--devices", src, dest) +func RsyncLocal(ctx context.Context, src string, dest string) error { + err := RunCommand(ctx, "rsync", "-aHASX", "--devices", src, dest) if err != nil { return fmt.Errorf("Failed to copy %q to %q: %w", src, dest, err) } diff --git a/sources/almalinux-http.go b/sources/almalinux-http.go index a3bb2e14..64989e31 100644 --- a/sources/almalinux-http.go +++ b/sources/almalinux-http.go @@ -111,7 +111,7 @@ func (s *almalinux) Run() error { } func (s *almalinux) rawRunner() error { - err := shared.RunScript(fmt.Sprintf(`#!/bin/sh + err := shared.RunScript(s.ctx, fmt.Sprintf(`#!/bin/sh set -eux # Create required files @@ -130,7 +130,7 @@ func (s *almalinux) rawRunner() error { } func (s *almalinux) isoRunner(gpgKeysPath string) error { - err := shared.RunScript(fmt.Sprintf(`#!/bin/sh + err := shared.RunScript(s.ctx, fmt.Sprintf(`#!/bin/sh set -eux GPG_KEYS="%s" diff --git a/sources/alpine-http.go b/sources/alpine-http.go index 66438d3f..d6e6f273 100644 --- a/sources/alpine-http.go +++ b/sources/alpine-http.go @@ -96,13 +96,13 @@ func (s *alpineLinux) Run() error { return fmt.Errorf("Failed to set up chroot: %w", err) } - err = shared.RunCommand("sed", "-i", "-e", "s/v[[:digit:]]\\.[[:digit:]]\\+/edge/g", "/etc/apk/repositories") + err = shared.RunCommand(s.ctx, "sed", "-i", "-e", "s/v[[:digit:]]\\.[[:digit:]]\\+/edge/g", "/etc/apk/repositories") if err != nil { exitChroot() return fmt.Errorf("Failed to edit apk repositories: %w", err) } - err = shared.RunCommand("apk", "upgrade", "--update-cache", "--available") + err = shared.RunCommand(s.ctx, "apk", "upgrade", "--update-cache", "--available") if err != nil { exitChroot() return fmt.Errorf("Failed to upgrade edge build: %w", err) diff --git a/sources/busybox.go b/sources/busybox.go index b1967e9f..157fcac9 100644 --- a/sources/busybox.go +++ b/sources/busybox.go @@ -54,7 +54,7 @@ func (s *busybox) Run() error { sourceDir = filepath.Join(sourceDir, fmt.Sprintf("busybox-%s", s.definition.Image.Release)) - err = shared.RunScript(fmt.Sprintf(`#!/bin/sh + err = shared.RunScript(s.ctx, fmt.Sprintf(`#!/bin/sh set -eux source_dir=%s diff --git a/sources/centos-http.go b/sources/centos-http.go index c5c0e296..c778dbfd 100644 --- a/sources/centos-http.go +++ b/sources/centos-http.go @@ -123,7 +123,7 @@ func (s *centOS) Run() error { } func (s *centOS) rawRunner() error { - err := shared.RunScript(fmt.Sprintf(`#!/bin/sh + err := shared.RunScript(s.ctx, fmt.Sprintf(`#!/bin/sh set -eux version="%s" @@ -156,7 +156,7 @@ fi } func (s *centOS) isoRunner(gpgKeysPath string) error { - err := shared.RunScript(fmt.Sprintf(`#!/bin/sh + err := shared.RunScript(s.ctx, fmt.Sprintf(`#!/bin/sh set -eux GPG_KEYS="%s" diff --git a/sources/common.go b/sources/common.go index 475ee325..f4d263de 100644 --- a/sources/common.go +++ b/sources/common.go @@ -1,6 +1,7 @@ package sources import ( + "context" "fmt" "hash" "io" @@ -25,14 +26,16 @@ type common struct { rootfsDir string cacheDir string sourcesDir string + ctx context.Context } -func (s *common) init(logger *zap.SugaredLogger, definition shared.Definition, rootfsDir string, cacheDir string, sourcesDir string) { +func (s *common) init(ctx context.Context, logger *zap.SugaredLogger, definition shared.Definition, rootfsDir string, cacheDir string, sourcesDir string) { s.logger = logger s.definition = definition s.rootfsDir = rootfsDir s.cacheDir = cacheDir s.sourcesDir = sourcesDir + s.ctx = ctx } func (s *common) getTargetDir() string { diff --git a/sources/debootstrap.go b/sources/debootstrap.go index 5c864989..5a123d06 100644 --- a/sources/debootstrap.go +++ b/sources/debootstrap.go @@ -90,7 +90,7 @@ func (s *debootstrap) Run() error { defer os.Remove(scriptPath) } - err := shared.RunCommand("debootstrap", args...) + err := shared.RunCommand(s.ctx, "debootstrap", args...) if err != nil { return fmt.Errorf(`Failed to run "debootstrap": %w`, err) } diff --git a/sources/oraclelinux-http.go b/sources/oraclelinux-http.go index 81c32201..61477322 100644 --- a/sources/oraclelinux-http.go +++ b/sources/oraclelinux-http.go @@ -105,7 +105,7 @@ func (s *oraclelinux) unpackISO(latestUpdate, filePath, rootfsDir string) error } // this is easier than doing the whole loop thing ourselves - err := shared.RunCommand("mount", "-o", "ro", filePath, isoDir) + err := shared.RunCommand(s.ctx, "mount", "-o", "ro", filePath, isoDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", filePath, err) } @@ -117,7 +117,7 @@ func (s *oraclelinux) unpackISO(latestUpdate, filePath, rootfsDir string) error // The squashfs.img contains an image containing the rootfs, so first // mount squashfs.img - err = shared.RunCommand("mount", "-o", "ro", squashfsImage, squashfsDir) + err = shared.RunCommand(s.ctx, "mount", "-o", "ro", squashfsImage, squashfsDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", squashfsImage, err) } @@ -215,7 +215,7 @@ func (s *oraclelinux) unpackISO(latestUpdate, filePath, rootfsDir string) error } } - err = shared.RunScript(fmt.Sprintf(`#!/bin/sh + err = shared.RunScript(s.ctx, fmt.Sprintf(`#!/bin/sh set -eux version="%s" @@ -305,7 +305,7 @@ EOF exitChroot() - err = shared.RsyncLocal(tempRootDir+"/rootfs/", rootfsDir) + err = shared.RsyncLocal(s.ctx, tempRootDir+"/rootfs/", rootfsDir) if err != nil { return fmt.Errorf(`Failed to run "rsync": %w`, err) } diff --git a/sources/plamolinux-http.go b/sources/plamolinux-http.go index 7ffe616e..9035f466 100644 --- a/sources/plamolinux-http.go +++ b/sources/plamolinux-http.go @@ -75,7 +75,7 @@ func (s *plamolinux) Run() error { return errors.New("Found more than one matching package") } - err = shared.RunCommand("tar", "-pxf", matches[0], "-C", pkgDir, "sbin/") + err = shared.RunCommand(s.ctx, "tar", "-pxf", matches[0], "-C", pkgDir, "sbin/") if err != nil { return fmt.Errorf("Failed to unpack %q: %w", matches[0], err) } @@ -85,7 +85,7 @@ func (s *plamolinux) Run() error { return fmt.Errorf("Failed to get absolute path: %w", err) } - err = shared.RunScript(fmt.Sprintf(`#!/bin/sh + err = shared.RunScript(s.ctx, fmt.Sprintf(`#!/bin/sh set -eux # Input variables diff --git a/sources/rhel-common.go b/sources/rhel-common.go index 9dc79650..3fb0a8bc 100644 --- a/sources/rhel-common.go +++ b/sources/rhel-common.go @@ -38,7 +38,7 @@ func (c *commonRHEL) unpackISO(filePath, rootfsDir string, scriptRunner func(str defer os.RemoveAll(tempRootDir) // this is easier than doing the whole loop thing ourselves - err = shared.RunCommand("mount", "-o", "ro", filePath, isoDir) + err = shared.RunCommand(c.ctx, "mount", "-o", "ro", filePath, isoDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", filePath, err) } @@ -49,7 +49,7 @@ func (c *commonRHEL) unpackISO(filePath, rootfsDir string, scriptRunner func(str if lxd.PathExists(squashfsImage) { // The squashfs.img contains an image containing the rootfs, so first // mount squashfs.img - err = shared.RunCommand("mount", "-o", "ro", squashfsImage, squashfsDir) + err = shared.RunCommand(c.ctx, "mount", "-o", "ro", squashfsImage, squashfsDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", squashfsImage, err) } @@ -94,12 +94,12 @@ func (c *commonRHEL) unpackISO(filePath, rootfsDir string, scriptRunner func(str } // Copy repo relevant files to the cdrom - err = shared.RsyncLocal(packagesDir, filepath.Join(tempRootDir, "mnt", "cdrom")) + err = shared.RsyncLocal(c.ctx, packagesDir, filepath.Join(tempRootDir, "mnt", "cdrom")) if err != nil { return fmt.Errorf("Failed to copy Packages: %w", err) } - err = shared.RsyncLocal(repodataDir, filepath.Join(tempRootDir, "mnt", "cdrom")) + err = shared.RsyncLocal(c.ctx, repodataDir, filepath.Join(tempRootDir, "mnt", "cdrom")) if err != nil { return fmt.Errorf("Failed to copy repodata: %w", err) } @@ -118,7 +118,7 @@ func (c *commonRHEL) unpackISO(filePath, rootfsDir string, scriptRunner func(str } gpgKeysPath += fmt.Sprintf("file:///mnt/cdrom/%s", filepath.Base(key)) - err = shared.RsyncLocal(key, filepath.Join(tempRootDir, "mnt", "cdrom")) + err = shared.RsyncLocal(c.ctx, key, filepath.Join(tempRootDir, "mnt", "cdrom")) if err != nil { return fmt.Errorf(`Failed to run "rsync": %w`, err) } @@ -139,7 +139,7 @@ func (c *commonRHEL) unpackISO(filePath, rootfsDir string, scriptRunner func(str exitChroot() - err = shared.RsyncLocal(tempRootDir+"/rootfs/", rootfsDir) + err = shared.RsyncLocal(c.ctx, tempRootDir+"/rootfs/", rootfsDir) if err != nil { return fmt.Errorf(`Failed to run "rsync": %w`, err) } @@ -154,7 +154,7 @@ func (c *commonRHEL) unpackRootfsImage(imageFile string, target string) error { } defer os.RemoveAll(installDir) - err = shared.RunCommand("mount", "-o", "ro", imageFile, installDir) + err = shared.RunCommand(c.ctx, "mount", "-o", "ro", imageFile, installDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", imageFile, err) } @@ -170,7 +170,7 @@ func (c *commonRHEL) unpackRootfsImage(imageFile string, target string) error { } defer os.RemoveAll(rootfsDir) - err = shared.RunCommand("mount", "-o", "ro", rootfsFile, rootfsDir) + err = shared.RunCommand(c.ctx, "mount", "-o", "ro", rootfsFile, rootfsDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", rootfsFile, err) } @@ -179,7 +179,7 @@ func (c *commonRHEL) unpackRootfsImage(imageFile string, target string) error { // Since rootfs is read-only, we need to copy it to a temporary rootfs // directory in order to create the minimal rootfs. - err = shared.RsyncLocal(rootfsDir+"/", target) + err = shared.RsyncLocal(c.ctx, rootfsDir+"/", target) if err != nil { return fmt.Errorf(`Failed to run "rsync": %w`, err) } @@ -198,7 +198,7 @@ func (c *commonRHEL) unpackRaw(filePath, rootfsDir string, scriptRunner func() e if strings.HasSuffix(filePath, ".raw.xz") { // Uncompress raw image - err := shared.RunCommand("unxz", filePath) + err := shared.RunCommand(c.ctx, "unxz", filePath) if err != nil { return fmt.Errorf(`Failed to run "unxz": %w`, err) } @@ -223,7 +223,7 @@ func (c *commonRHEL) unpackRaw(filePath, rootfsDir string, scriptRunner func() e } // Mount the partition read-only since we don't want to accidently modify it. - err = shared.RunCommand("mount", "-o", fmt.Sprintf("ro,loop,offset=%d", offset*512), + err = shared.RunCommand(c.ctx, "mount", "-o", fmt.Sprintf("ro,loop,offset=%d", offset*512), rawFilePath, roRootDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", rawFilePath, err) @@ -232,7 +232,7 @@ func (c *commonRHEL) unpackRaw(filePath, rootfsDir string, scriptRunner func() e // Since roRootDir is read-only, we need to copy it to a temporary rootfs // directory in order to create the minimal rootfs. - err = shared.RsyncLocal(roRootDir+"/", tempRootDir) + err = shared.RsyncLocal(c.ctx, roRootDir+"/", tempRootDir) if err != nil { return fmt.Errorf(`Failed to run "rsync": %w`, err) } @@ -251,7 +251,7 @@ func (c *commonRHEL) unpackRaw(filePath, rootfsDir string, scriptRunner func() e exitChroot() - err = shared.RsyncLocal(tempRootDir+"/rootfs/", rootfsDir) + err = shared.RsyncLocal(c.ctx, tempRootDir+"/rootfs/", rootfsDir) if err != nil { return fmt.Errorf(`Failed to run "rsync": %w`, err) } diff --git a/sources/rocky-http.go b/sources/rocky-http.go index 9aee2972..9ca9f5ec 100644 --- a/sources/rocky-http.go +++ b/sources/rocky-http.go @@ -77,7 +77,7 @@ func (s *rockylinux) Run() error { } func (s *rockylinux) isoRunner(gpgKeysPath string) error { - err := shared.RunScript(fmt.Sprintf(`#!/bin/sh + err := shared.RunScript(s.ctx, fmt.Sprintf(`#!/bin/sh set -eux GPG_KEYS="%s" RELEASE="%s" diff --git a/sources/source.go b/sources/source.go index f6bce4e1..f07a1434 100644 --- a/sources/source.go +++ b/sources/source.go @@ -1,6 +1,7 @@ package sources import ( + "context" "errors" "go.uber.org/zap" @@ -12,7 +13,7 @@ import ( var ErrUnknownDownloader = errors.New("Unknown downloader") type downloader interface { - init(logger *zap.SugaredLogger, definition shared.Definition, rootfsDir string, cacheDir string, sourcesDir string) + init(ctx context.Context, logger *zap.SugaredLogger, definition shared.Definition, rootfsDir string, cacheDir string, sourcesDir string) Downloader } @@ -48,7 +49,7 @@ var downloaders = map[string]func() downloader{ } // Load loads and initializes a downloader. -func Load(downloaderName string, logger *zap.SugaredLogger, definition shared.Definition, rootfsDir string, cacheDir string, sourcesDir string) (Downloader, error) { +func Load(ctx context.Context, downloaderName string, logger *zap.SugaredLogger, definition shared.Definition, rootfsDir string, cacheDir string, sourcesDir string) (Downloader, error) { df, ok := downloaders[downloaderName] if !ok { return nil, ErrUnknownDownloader @@ -56,7 +57,7 @@ func Load(downloaderName string, logger *zap.SugaredLogger, definition shared.De d := df() - d.init(logger, definition, rootfsDir, cacheDir, sourcesDir) + d.init(ctx, logger, definition, rootfsDir, cacheDir, sourcesDir) return d, nil } diff --git a/sources/springdalelinux-http.go b/sources/springdalelinux-http.go index e42bee41..a1a4e6c7 100644 --- a/sources/springdalelinux-http.go +++ b/sources/springdalelinux-http.go @@ -43,7 +43,7 @@ func (s *springdalelinux) Run() error { } func (s *springdalelinux) isoRunner(gpgKeysPath string) error { - err := shared.RunScript(fmt.Sprintf(`#!/bin/sh + err := shared.RunScript(s.ctx, fmt.Sprintf(`#!/bin/sh set -eux GPG_KEYS="%s" diff --git a/sources/ubuntu-http.go b/sources/ubuntu-http.go index 04fbadf6..69faf3c5 100644 --- a/sources/ubuntu-http.go +++ b/sources/ubuntu-http.go @@ -49,7 +49,7 @@ func (s *ubuntu) runDefaultVariant(definition shared.Definition, rootfsDir strin func (s *ubuntu) runCoreVariant(definition shared.Definition, rootfsDir string) error { if !lxd.PathExists(filepath.Join(s.fpath, strings.TrimSuffix(s.fname, ".xz"))) { - err := shared.RunCommand("unxz", "-k", filepath.Join(s.fpath, s.fname)) + err := shared.RunCommand(s.ctx, "unxz", "-k", filepath.Join(s.fpath, s.fname)) if err != nil { return fmt.Errorf(`Failed to run "unxz": %w`, err) } @@ -81,13 +81,13 @@ func (s *ubuntu) runCoreVariant(definition shared.Definition, rootfsDir string) } } - err = shared.RunCommand("mount", "-o", fmt.Sprintf("loop,offset=%d", offset*512), f, imageDir) + err = shared.RunCommand(s.ctx, "mount", "-o", fmt.Sprintf("loop,offset=%d", offset*512), f, imageDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", fmt.Sprintf("loop,offset=%d", offset*512), err) } defer unix.Unmount(imageDir, 0) - err = shared.RsyncLocal(filepath.Join(imageDir, "system-data"), rootfsDir) + err = shared.RsyncLocal(s.ctx, filepath.Join(imageDir, "system-data"), rootfsDir) if err != nil { return fmt.Errorf(`Failed to run "rsync": %w`, err) } @@ -151,7 +151,7 @@ func (s *ubuntu) runCoreVariant(definition shared.Definition, rootfsDir string) return fmt.Errorf("Failed to create chroot: %w", err) } - err = shared.RunScript(`#!/bin/sh + err = shared.RunScript(s.ctx, `#!/bin/sh apt-get update apt-get install -y busybox-static fuse util-linux squashfuse `) From d24024c4882fdc2e18235ba13f73ca55eed87dd8 Mon Sep 17 00:00:00 2001 From: Thomas Hipp Date: Wed, 17 Nov 2021 16:15:59 +0100 Subject: [PATCH 3/5] *: Add stdin and stdout to RunCommand Signed-off-by: Thomas Hipp --- distrobuilder/vm.go | 22 +++++++++++----------- image/lxd.go | 4 ++-- managers/common.go | 10 +++++----- managers/equo.go | 4 ++-- managers/pacman.go | 4 ++-- managers/zypper.go | 2 +- shared/util.go | 28 +++++++++++++++++----------- sources/alpine-http.go | 4 ++-- sources/debootstrap.go | 2 +- sources/oraclelinux-http.go | 4 ++-- sources/plamolinux-http.go | 2 +- sources/rhel-common.go | 12 ++++++------ sources/ubuntu-http.go | 4 ++-- 13 files changed, 54 insertions(+), 48 deletions(-) diff --git a/distrobuilder/vm.go b/distrobuilder/vm.go index e855f3bc..304fa127 100644 --- a/distrobuilder/vm.go +++ b/distrobuilder/vm.go @@ -88,7 +88,7 @@ func (v *vm) createPartitions() error { } for _, cmd := range args { - err := shared.RunCommand(v.ctx, "sgdisk", append([]string{v.imageFile}, cmd...)...) + err := shared.RunCommand(v.ctx, nil, nil, "sgdisk", append([]string{v.imageFile}, cmd...)...) if err != nil { return fmt.Errorf("Failed to create partitions: %w", err) } @@ -171,7 +171,7 @@ func (v *vm) umountImage() error { return nil } - err := shared.RunCommand(v.ctx, "losetup", "-d", v.loopDevice) + err := shared.RunCommand(v.ctx, nil, nil, "losetup", "-d", v.loopDevice) if err != nil { return fmt.Errorf("Failed to detach loop device: %w", err) } @@ -203,21 +203,21 @@ func (v *vm) createRootFS() error { switch v.rootFS { case "btrfs": - err := shared.RunCommand(v.ctx, "mkfs.btrfs", "-f", "-L", "rootfs", v.getRootfsDevFile()) + err := shared.RunCommand(v.ctx, nil, nil, "mkfs.btrfs", "-f", "-L", "rootfs", v.getRootfsDevFile()) if err != nil { return fmt.Errorf("Failed to create btrfs filesystem: %w", err) } // Create the root subvolume as well - err = shared.RunCommand(v.ctx, "mount", v.getRootfsDevFile(), v.rootfsDir) + err = shared.RunCommand(v.ctx, nil, nil, "mount", v.getRootfsDevFile(), v.rootfsDir) if err != nil { return fmt.Errorf("Failed to mount %q at %q: %w", v.getRootfsDevFile(), v.rootfsDir, err) } - defer shared.RunCommand(v.ctx, "umount", v.rootfsDir) + defer shared.RunCommand(v.ctx, nil, nil, "umount", v.rootfsDir) - return shared.RunCommand(v.ctx, "btrfs", "subvolume", "create", fmt.Sprintf("%s/@", v.rootfsDir)) + return shared.RunCommand(v.ctx, nil, nil, "btrfs", "subvolume", "create", fmt.Sprintf("%s/@", v.rootfsDir)) case "ext4": - return shared.RunCommand(v.ctx, "mkfs.ext4", "-F", "-b", "4096", "-i 8192", "-m", "0", "-L", "rootfs", "-E", "resize=536870912", v.getRootfsDevFile()) + return shared.RunCommand(v.ctx, nil, nil, "mkfs.ext4", "-F", "-b", "4096", "-i 8192", "-m", "0", "-L", "rootfs", "-E", "resize=536870912", v.getRootfsDevFile()) } return nil @@ -228,7 +228,7 @@ func (v *vm) createUEFIFS() error { return errors.New("Disk image not mounted") } - return shared.RunCommand(v.ctx, "mkfs.vfat", "-F", "32", "-n", "UEFI", v.getUEFIDevFile()) + return shared.RunCommand(v.ctx, nil, nil, "mkfs.vfat", "-F", "32", "-n", "UEFI", v.getUEFIDevFile()) } func (v *vm) getRootfsPartitionUUID() (string, error) { @@ -264,9 +264,9 @@ func (v *vm) mountRootPartition() error { switch v.rootFS { case "btrfs": - return shared.RunCommand(v.ctx, "mount", v.getRootfsDevFile(), v.rootfsDir, "-o", "defaults,subvol=/@") + return shared.RunCommand(v.ctx, nil, nil, "mount", v.getRootfsDevFile(), v.rootfsDir, "-o", "defaults,subvol=/@") case "ext4": - return shared.RunCommand(v.ctx, "mount", v.getRootfsDevFile(), v.rootfsDir) + return shared.RunCommand(v.ctx, nil, nil, "mount", v.getRootfsDevFile(), v.rootfsDir) } @@ -285,5 +285,5 @@ func (v *vm) mountUEFIPartition() error { return fmt.Errorf("Failed to create directory %q: %w", mountpoint, err) } - return shared.RunCommand(v.ctx, "mount", v.getUEFIDevFile(), mountpoint) + return shared.RunCommand(v.ctx, nil, nil, "mount", v.getUEFIDevFile(), mountpoint) } diff --git a/image/lxd.go b/image/lxd.go index 7e3624af..b287edea 100644 --- a/image/lxd.go +++ b/image/lxd.go @@ -84,7 +84,7 @@ func (l *LXDImage) Build(unified bool, compression string, vm bool) (string, str if vm { // Create compressed qcow2 image. - err = shared.RunCommand(l.ctx, "qemu-img", "convert", "-c", "-O", "qcow2", "-o", "compat=0.10", + err = shared.RunCommand(l.ctx, nil, nil, "qemu-img", "convert", "-c", "-O", "qcow2", "-o", "compat=0.10", rawImage, qcowImage) if err != nil { @@ -138,7 +138,7 @@ func (l *LXDImage) Build(unified bool, compression string, vm bool) (string, str rootfsFile = filepath.Join(l.targetDir, "rootfs.squashfs") // Create rootfs as squashfs. - err = shared.RunCommand(l.ctx, "mksquashfs", l.sourceDir, + err = shared.RunCommand(l.ctx, nil, nil, "mksquashfs", l.sourceDir, rootfsFile, "-noappend", "-comp", compression, "-b", "1M", "-no-progress", "-no-recovery") } diff --git a/managers/common.go b/managers/common.go index 56908252..2bcb0016 100644 --- a/managers/common.go +++ b/managers/common.go @@ -32,7 +32,7 @@ func (c *common) install(pkgs, flags []string) error { args = append(args, flags...) args = append(args, pkgs...) - return shared.RunCommand(c.ctx, c.commands.install, args...) + return shared.RunCommand(c.ctx, nil, nil, c.commands.install, args...) } // Remove removes packages from the rootfs. @@ -45,7 +45,7 @@ func (c *common) remove(pkgs, flags []string) error { args = append(args, flags...) args = append(args, pkgs...) - return shared.RunCommand(c.ctx, c.commands.remove, args...) + return shared.RunCommand(c.ctx, nil, nil, c.commands.remove, args...) } // Clean cleans up cached files used by the package managers. @@ -58,7 +58,7 @@ func (c *common) clean() error { args := append(c.flags.global, c.flags.clean...) - err = shared.RunCommand(c.ctx, c.commands.clean, args...) + err = shared.RunCommand(c.ctx, nil, nil, c.commands.clean, args...) if err != nil { return err } @@ -85,7 +85,7 @@ func (c *common) refresh() error { args := append(c.flags.global, c.flags.refresh...) - return shared.RunCommand(c.ctx, c.commands.refresh, args...) + return shared.RunCommand(c.ctx, nil, nil, c.commands.refresh, args...) } // Update updates all packages. @@ -96,7 +96,7 @@ func (c *common) update() error { args := append(c.flags.global, c.flags.update...) - return shared.RunCommand(c.ctx, c.commands.update, args...) + return shared.RunCommand(c.ctx, nil, nil, c.commands.update, args...) } // SetInstallFlags overrides the default install flags. diff --git a/managers/equo.go b/managers/equo.go index bd669e23..d52b1f62 100644 --- a/managers/equo.go +++ b/managers/equo.go @@ -66,7 +66,7 @@ func (m *equo) enmanRepoCaller(repo shared.DefinitionPackagesRepository) error { args = append(args, repo.Name) } - return shared.RunCommand(m.ctx, "enman", args...) + return shared.RunCommand(m.ctx, nil, nil, "enman", args...) } func (m *equo) equoRepoCaller(repo shared.DefinitionPackagesRepository) error { @@ -78,6 +78,6 @@ func (m *equo) equoRepoCaller(repo shared.DefinitionPackagesRepository) error { return errors.New("Invalid repository url") } - return shared.RunCommand(m.ctx, "equo", "repo", "add", "--repo", repo.URL, "--pkg", repo.URL, + return shared.RunCommand(m.ctx, nil, nil, "equo", "repo", "add", "--repo", repo.URL, "--pkg", repo.URL, repo.Name) } diff --git a/managers/pacman.go b/managers/pacman.go index 36d7cc2a..908f5281 100644 --- a/managers/pacman.go +++ b/managers/pacman.go @@ -94,7 +94,7 @@ func (m *pacman) setupTrustedKeys() error { return nil } - err = shared.RunCommand(m.ctx, "pacman-key", "--init") + err = shared.RunCommand(m.ctx, nil, nil, "pacman-key", "--init") if err != nil { return fmt.Errorf("Error initializing with pacman-key: %w", err) } @@ -107,7 +107,7 @@ func (m *pacman) setupTrustedKeys() error { keyring = "archlinux" } - err = shared.RunCommand(m.ctx, "pacman-key", "--populate", keyring) + err = shared.RunCommand(m.ctx, nil, nil, "pacman-key", "--populate", keyring) if err != nil { return fmt.Errorf("Error populating with pacman-key: %w", err) } diff --git a/managers/zypper.go b/managers/zypper.go index 2174dbd1..8668bcbb 100644 --- a/managers/zypper.go +++ b/managers/zypper.go @@ -60,5 +60,5 @@ func (m *zypper) manageRepository(repoAction shared.DefinitionPackagesRepository return errors.New("Invalid repository url") } - return shared.RunCommand(m.ctx, "zypper", "ar", "--refresh", "--check", repoAction.URL, repoAction.Name) + return shared.RunCommand(m.ctx, nil, nil, "zypper", "ar", "--refresh", "--check", repoAction.URL, repoAction.Name) } diff --git a/shared/util.go b/shared/util.go index 1e1c3202..e87edf28 100644 --- a/shared/util.go +++ b/shared/util.go @@ -49,14 +49,20 @@ func Copy(src, dest string) error { return destFile.Sync() } -// RunCommand runs a command hereby setting the SHELL and PATH env variables, -// and redirecting the process's stdout and stderr to the real stdout and stderr -// respectively. -func RunCommand(ctx context.Context, name string, arg ...string) error { +// RunCommand runs a command. Stdout is written to the given io.Writer. If nil, it's written to the real stdout. Stderr is always written to the real stderr. +func RunCommand(ctx context.Context, stdin io.Reader, stdout io.Writer, name string, arg ...string) error { cmd := exec.CommandContext(ctx, name, arg...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout + if stdin != nil { + cmd.Stdin = stdin + } + + if stdout != nil { + cmd.Stdout = stdout + } else { + cmd.Stdout = os.Stdout + } + cmd.Stderr = os.Stderr return cmd.Run() @@ -79,12 +85,12 @@ func RunScript(ctx context.Context, content string) error { fdPath := fmt.Sprintf("/proc/self/fd/%d", fd) - return RunCommand(ctx, fdPath) + return RunCommand(ctx, nil, nil, fdPath) } // Pack creates an uncompressed tarball. func Pack(ctx context.Context, filename, compression, path string, args ...string) (string, error) { - err := RunCommand(ctx, "tar", append([]string{"--xattrs", "-cf", filename, "-C", path}, args...)...) + err := RunCommand(ctx, nil, nil, "tar", append([]string{"--xattrs", "-cf", filename, "-C", path}, args...)...) if err != nil { // Clean up incomplete tarball os.Remove(filename) @@ -96,7 +102,7 @@ func Pack(ctx context.Context, filename, compression, path string, args ...strin // PackUpdate updates an existing tarball. func PackUpdate(ctx context.Context, filename, compression, path string, args ...string) (string, error) { - err := RunCommand(ctx, "tar", append([]string{"--xattrs", "-uf", filename, "-C", path}, args...)...) + err := RunCommand(ctx, nil, nil, "tar", append([]string{"--xattrs", "-uf", filename, "-C", path}, args...)...) if err != nil { return "", fmt.Errorf("Failed to update tarball: %w", err) } @@ -114,7 +120,7 @@ func compressTarball(ctx context.Context, filename, compression string) (string, defer os.Remove(filename) fallthrough case "bzip2", "xz", "lzip", "lzma", "gzip": - err := RunCommand(ctx, compression, "-f", filename) + err := RunCommand(ctx, nil, nil, compression, "-f", filename) if err != nil { return "", fmt.Errorf("Failed to compress tarball %q: %w", filename, err) } @@ -237,7 +243,7 @@ func SetEnvVariables(env Environment) Environment { // RsyncLocal copies src to dest using rsync. func RsyncLocal(ctx context.Context, src string, dest string) error { - err := RunCommand(ctx, "rsync", "-aHASX", "--devices", src, dest) + err := RunCommand(ctx, nil, nil, "rsync", "-aHASX", "--devices", src, dest) if err != nil { return fmt.Errorf("Failed to copy %q to %q: %w", src, dest, err) } diff --git a/sources/alpine-http.go b/sources/alpine-http.go index d6e6f273..f7534146 100644 --- a/sources/alpine-http.go +++ b/sources/alpine-http.go @@ -96,13 +96,13 @@ func (s *alpineLinux) Run() error { return fmt.Errorf("Failed to set up chroot: %w", err) } - err = shared.RunCommand(s.ctx, "sed", "-i", "-e", "s/v[[:digit:]]\\.[[:digit:]]\\+/edge/g", "/etc/apk/repositories") + err = shared.RunCommand(s.ctx, nil, nil, "sed", "-i", "-e", "s/v[[:digit:]]\\.[[:digit:]]\\+/edge/g", "/etc/apk/repositories") if err != nil { exitChroot() return fmt.Errorf("Failed to edit apk repositories: %w", err) } - err = shared.RunCommand(s.ctx, "apk", "upgrade", "--update-cache", "--available") + err = shared.RunCommand(s.ctx, nil, nil, "apk", "upgrade", "--update-cache", "--available") if err != nil { exitChroot() return fmt.Errorf("Failed to upgrade edge build: %w", err) diff --git a/sources/debootstrap.go b/sources/debootstrap.go index 5a123d06..4c0ae78d 100644 --- a/sources/debootstrap.go +++ b/sources/debootstrap.go @@ -90,7 +90,7 @@ func (s *debootstrap) Run() error { defer os.Remove(scriptPath) } - err := shared.RunCommand(s.ctx, "debootstrap", args...) + err := shared.RunCommand(s.ctx, nil, nil, "debootstrap", args...) if err != nil { return fmt.Errorf(`Failed to run "debootstrap": %w`, err) } diff --git a/sources/oraclelinux-http.go b/sources/oraclelinux-http.go index 61477322..86cfec1e 100644 --- a/sources/oraclelinux-http.go +++ b/sources/oraclelinux-http.go @@ -105,7 +105,7 @@ func (s *oraclelinux) unpackISO(latestUpdate, filePath, rootfsDir string) error } // this is easier than doing the whole loop thing ourselves - err := shared.RunCommand(s.ctx, "mount", "-o", "ro", filePath, isoDir) + err := shared.RunCommand(s.ctx, nil, nil, "mount", "-o", "ro", filePath, isoDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", filePath, err) } @@ -117,7 +117,7 @@ func (s *oraclelinux) unpackISO(latestUpdate, filePath, rootfsDir string) error // The squashfs.img contains an image containing the rootfs, so first // mount squashfs.img - err = shared.RunCommand(s.ctx, "mount", "-o", "ro", squashfsImage, squashfsDir) + err = shared.RunCommand(s.ctx, nil, nil, "mount", "-o", "ro", squashfsImage, squashfsDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", squashfsImage, err) } diff --git a/sources/plamolinux-http.go b/sources/plamolinux-http.go index 9035f466..4e2e6e69 100644 --- a/sources/plamolinux-http.go +++ b/sources/plamolinux-http.go @@ -75,7 +75,7 @@ func (s *plamolinux) Run() error { return errors.New("Found more than one matching package") } - err = shared.RunCommand(s.ctx, "tar", "-pxf", matches[0], "-C", pkgDir, "sbin/") + err = shared.RunCommand(s.ctx, nil, nil, "tar", "-pxf", matches[0], "-C", pkgDir, "sbin/") if err != nil { return fmt.Errorf("Failed to unpack %q: %w", matches[0], err) } diff --git a/sources/rhel-common.go b/sources/rhel-common.go index 3fb0a8bc..1ddc7983 100644 --- a/sources/rhel-common.go +++ b/sources/rhel-common.go @@ -38,7 +38,7 @@ func (c *commonRHEL) unpackISO(filePath, rootfsDir string, scriptRunner func(str defer os.RemoveAll(tempRootDir) // this is easier than doing the whole loop thing ourselves - err = shared.RunCommand(c.ctx, "mount", "-o", "ro", filePath, isoDir) + err = shared.RunCommand(c.ctx, nil, nil, "mount", "-o", "ro", filePath, isoDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", filePath, err) } @@ -49,7 +49,7 @@ func (c *commonRHEL) unpackISO(filePath, rootfsDir string, scriptRunner func(str if lxd.PathExists(squashfsImage) { // The squashfs.img contains an image containing the rootfs, so first // mount squashfs.img - err = shared.RunCommand(c.ctx, "mount", "-o", "ro", squashfsImage, squashfsDir) + err = shared.RunCommand(c.ctx, nil, nil, "mount", "-o", "ro", squashfsImage, squashfsDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", squashfsImage, err) } @@ -154,7 +154,7 @@ func (c *commonRHEL) unpackRootfsImage(imageFile string, target string) error { } defer os.RemoveAll(installDir) - err = shared.RunCommand(c.ctx, "mount", "-o", "ro", imageFile, installDir) + err = shared.RunCommand(c.ctx, nil, nil, "mount", "-o", "ro", imageFile, installDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", imageFile, err) } @@ -170,7 +170,7 @@ func (c *commonRHEL) unpackRootfsImage(imageFile string, target string) error { } defer os.RemoveAll(rootfsDir) - err = shared.RunCommand(c.ctx, "mount", "-o", "ro", rootfsFile, rootfsDir) + err = shared.RunCommand(c.ctx, nil, nil, "mount", "-o", "ro", rootfsFile, rootfsDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", rootfsFile, err) } @@ -198,7 +198,7 @@ func (c *commonRHEL) unpackRaw(filePath, rootfsDir string, scriptRunner func() e if strings.HasSuffix(filePath, ".raw.xz") { // Uncompress raw image - err := shared.RunCommand(c.ctx, "unxz", filePath) + err := shared.RunCommand(c.ctx, nil, nil, "unxz", filePath) if err != nil { return fmt.Errorf(`Failed to run "unxz": %w`, err) } @@ -223,7 +223,7 @@ func (c *commonRHEL) unpackRaw(filePath, rootfsDir string, scriptRunner func() e } // Mount the partition read-only since we don't want to accidently modify it. - err = shared.RunCommand(c.ctx, "mount", "-o", fmt.Sprintf("ro,loop,offset=%d", offset*512), + err = shared.RunCommand(c.ctx, nil, nil, "mount", "-o", fmt.Sprintf("ro,loop,offset=%d", offset*512), rawFilePath, roRootDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", rawFilePath, err) diff --git a/sources/ubuntu-http.go b/sources/ubuntu-http.go index 69faf3c5..0dba4e6c 100644 --- a/sources/ubuntu-http.go +++ b/sources/ubuntu-http.go @@ -49,7 +49,7 @@ func (s *ubuntu) runDefaultVariant(definition shared.Definition, rootfsDir strin func (s *ubuntu) runCoreVariant(definition shared.Definition, rootfsDir string) error { if !lxd.PathExists(filepath.Join(s.fpath, strings.TrimSuffix(s.fname, ".xz"))) { - err := shared.RunCommand(s.ctx, "unxz", "-k", filepath.Join(s.fpath, s.fname)) + err := shared.RunCommand(s.ctx, nil, nil, "unxz", "-k", filepath.Join(s.fpath, s.fname)) if err != nil { return fmt.Errorf(`Failed to run "unxz": %w`, err) } @@ -81,7 +81,7 @@ func (s *ubuntu) runCoreVariant(definition shared.Definition, rootfsDir string) } } - err = shared.RunCommand(s.ctx, "mount", "-o", fmt.Sprintf("loop,offset=%d", offset*512), f, imageDir) + err = shared.RunCommand(s.ctx, nil, nil, "mount", "-o", fmt.Sprintf("loop,offset=%d", offset*512), f, imageDir) if err != nil { return fmt.Errorf("Failed to mount %q: %w", fmt.Sprintf("loop,offset=%d", offset*512), err) } From 7d6ea3775f04dc9db9044f8b1608eb662b519f0d Mon Sep 17 00:00:00 2001 From: Thomas Hipp Date: Wed, 17 Nov 2021 16:37:43 +0100 Subject: [PATCH 4/5] *: Replace lxd.RunCommand* with shared.RunCommand Signed-off-by: Thomas Hipp --- distrobuilder/main_lxd.go | 4 ++-- distrobuilder/main_repack-windows.go | 27 ++++++++++++++------------- distrobuilder/vm.go | 25 ++++++++++++++++--------- managers/apt.go | 4 ++-- managers/yum.go | 2 +- sources/busybox.go | 2 +- sources/common.go | 16 ++++++++++------ sources/rhel-common.go | 2 +- sources/ubuntu-http.go | 6 ++++-- 9 files changed, 51 insertions(+), 37 deletions(-) diff --git a/distrobuilder/main_lxd.go b/distrobuilder/main_lxd.go index e0bd3256..7fb5b8e2 100644 --- a/distrobuilder/main_lxd.go +++ b/distrobuilder/main_lxd.go @@ -290,7 +290,7 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string, overlayDir string) error if err != nil { return fmt.Errorf("failed to mount root partion: %w", err) } - defer lxd.RunCommand("umount", "-R", vmDir) + defer shared.RunCommand(vm.ctx, nil, nil, "umount", "-R", vmDir) err = vm.createUEFIFS() if err != nil { @@ -361,7 +361,7 @@ func (c *cmdLXD) run(cmd *cobra.Command, args []string, overlayDir string) error // Unmount VM directory and loop device before creating the image. if c.flagVM { - _, err := lxd.RunCommand("umount", "-R", vmDir) + err := shared.RunCommand(vm.ctx, nil, nil, "umount", "-R", vmDir) if err != nil { return fmt.Errorf("Failed to unmount %q: %w", vmDir, err) } diff --git a/distrobuilder/main_repack-windows.go b/distrobuilder/main_repack-windows.go index 165587a7..588ca742 100644 --- a/distrobuilder/main_repack-windows.go +++ b/distrobuilder/main_repack-windows.go @@ -160,7 +160,7 @@ func (c *cmdRepackWindows) preRun(cmd *cobra.Command, args []string) error { logger.Info("Mounting Windows ISO") // Mount ISO - _, err = lxd.RunCommand("mount", "-o", "loop", args[0], c.global.sourceDir) + err = shared.RunCommand(c.global.ctx, nil, nil, "mount", "-o", "loop", args[0], c.global.sourceDir) if err != nil { return fmt.Errorf("Failed to mount %q at %q: %w", args[0], c.global.sourceDir, err) } @@ -216,7 +216,7 @@ func (c *cmdRepackWindows) run(cmd *cobra.Command, args []string, overlayDir str logger.Info("Mounting driver ISO") // Mount driver ISO - _, err := lxd.RunCommand("mount", "-o", "loop", virtioISOPath, driverPath) + err := shared.RunCommand(c.global.ctx, nil, nil, "mount", "-o", "loop", virtioISOPath, driverPath) if err != nil { return fmt.Errorf("Failed to mount %q at %q: %w", virtioISOPath, driverPath, err) } @@ -268,7 +268,7 @@ func (c *cmdRepackWindows) run(cmd *cobra.Command, args []string, overlayDir str var buf bytes.Buffer - err = lxd.RunCommandWithFds(nil, &buf, "wimlib-imagex", "info", installWim) + err = shared.RunCommand(c.global.ctx, nil, &buf, "wimlib-imagex", "info", installWim) if err != nil { return fmt.Errorf("Failed to retrieve wim file information: %w", err) } @@ -305,18 +305,19 @@ func (c *cmdRepackWindows) run(cmd *cobra.Command, args []string, overlayDir str } logger.Info("Generating new ISO") + var stdout strings.Builder - stdout, err := lxd.RunCommand("genisoimage", "--version") + err = shared.RunCommand(c.global.ctx, nil, &stdout, "genisoimage", "--version") if err != nil { return fmt.Errorf("Failed to determine version of genisoimage: %w", err) } - version := strings.Split(stdout, "\n")[0] + version := strings.Split(stdout.String(), "\n")[0] if strings.HasPrefix(version, "mkisofs") { - _, err = lxd.RunCommand("genisoimage", "-iso-level", "3", "-l", "-no-emul-boot", "-b", "efi/microsoft/boot/efisys.bin", "-o", args[1], overlayDir) + err = shared.RunCommand(c.global.ctx, nil, nil, "genisoimage", "-iso-level", "3", "-l", "-no-emul-boot", "-b", "efi/microsoft/boot/efisys.bin", "-o", args[1], overlayDir) } else { - _, err = lxd.RunCommand("genisoimage", "--allow-limited-size", "-l", "-no-emul-boot", "-b", "efi/microsoft/boot/efisys.bin", "-o", args[1], overlayDir) + err = shared.RunCommand(c.global.ctx, nil, nil, "genisoimage", "--allow-limited-size", "-l", "-no-emul-boot", "-b", "efi/microsoft/boot/efisys.bin", "-o", args[1], overlayDir) } if err != nil { return fmt.Errorf("Failed to generate ISO: %w", err) @@ -341,13 +342,13 @@ func (c *cmdRepackWindows) modifyWim(path string, index int) error { success := false - _, err := lxd.RunCommand("wimlib-imagex", "mountrw", wimFile, strconv.Itoa(index), wimPath, "--allow-other") + err := shared.RunCommand(c.global.ctx, nil, nil, "wimlib-imagex", "mountrw", wimFile, strconv.Itoa(index), wimPath, "--allow-other") if err != nil { return fmt.Errorf("Failed to mount %q: %w", filepath.Base(wimFile), err) } defer func() { if !success { - lxd.RunCommand("wimlib-imagex", "unmount", wimPath) + shared.RunCommand(c.global.ctx, nil, nil, "wimlib-imagex", "unmount", wimPath) } }() @@ -380,7 +381,7 @@ func (c *cmdRepackWindows) modifyWim(path string, index int) error { return fmt.Errorf("Failed to inject drivers: %w", err) } - _, err = lxd.RunCommand("wimlib-imagex", "unmount", wimPath, "--commit") + err = shared.RunCommand(c.global.ctx, nil, nil, "wimlib-imagex", "unmount", wimPath, "--commit") if err != nil { return fmt.Errorf("Failed to unmount WIM image: %w", err) } @@ -642,21 +643,21 @@ func (c *cmdRepackWindows) injectDrivers(dirs map[string]string) error { logger.Debugw("Updating Windows registry", "hivefile", "DRIVERS") - err := lxd.RunCommandWithFds(strings.NewReader(driversRegistry), nil, "hivexregedit", "--merge", "--prefix='HKEY_LOCAL_MACHINE\\DRIVERS'", filepath.Join(dirs["config"], "DRIVERS")) + err := shared.RunCommand(c.global.ctx, strings.NewReader(driversRegistry), nil, "hivexregedit", "--merge", "--prefix='HKEY_LOCAL_MACHINE\\DRIVERS'", filepath.Join(dirs["config"], "DRIVERS")) if err != nil { return fmt.Errorf("Failed to edit Windows DRIVERS registry: %w", err) } logger.Debugw("Updating Windows registry", "hivefile", "SYSTEM") - err = lxd.RunCommandWithFds(strings.NewReader(systemRegistry), nil, "hivexregedit", "--merge", "--prefix='HKEY_LOCAL_MACHINE\\SYSTEM'", filepath.Join(dirs["config"], "SYSTEM")) + err = shared.RunCommand(c.global.ctx, strings.NewReader(systemRegistry), nil, "hivexregedit", "--merge", "--prefix='HKEY_LOCAL_MACHINE\\SYSTEM'", filepath.Join(dirs["config"], "SYSTEM")) if err != nil { return fmt.Errorf("Failed to edit Windows SYSTEM registry: %w", err) } logger.Debugw("Updating Windows registry", "hivefile", "SOFTWARE") - err = lxd.RunCommandWithFds(strings.NewReader(softwareRegistry), nil, "hivexregedit", "--merge", "--prefix='HKEY_LOCAL_MACHINE\\SOFTWARE'", filepath.Join(dirs["config"], "SOFTWARE")) + err = shared.RunCommand(c.global.ctx, strings.NewReader(softwareRegistry), nil, "hivexregedit", "--merge", "--prefix='HKEY_LOCAL_MACHINE\\SOFTWARE'", filepath.Join(dirs["config"], "SOFTWARE")) if err != nil { return fmt.Errorf("Failed to edit Windows SOFTWARE registry: %w", err) } diff --git a/distrobuilder/vm.go b/distrobuilder/vm.go index 304fa127..c0319d5c 100644 --- a/distrobuilder/vm.go +++ b/distrobuilder/vm.go @@ -103,22 +103,25 @@ func (v *vm) mountImage() error { return nil } - stdout, err := lxd.RunCommand("losetup", "-P", "-f", "--show", v.imageFile) + var out strings.Builder + + err := shared.RunCommand(v.ctx, nil, &out, "losetup", "-P", "-f", "--show", v.imageFile) if err != nil { return fmt.Errorf("Failed to setup loop device: %w", err) } - v.loopDevice = strings.TrimSpace(stdout) + v.loopDevice = strings.TrimSpace(out.String()) + + out.Reset() // Ensure the partitions are accessible. This part is usually only needed // if building inside of a container. - - out, err := lxd.RunCommand("lsblk", "--raw", "--output", "MAJ:MIN", "--noheadings", v.loopDevice) + err = shared.RunCommand(v.ctx, nil, &out, "lsblk", "--raw", "--output", "MAJ:MIN", "--noheadings", v.loopDevice) if err != nil { return fmt.Errorf("Failed to list block devices: %w", err) } - deviceNumbers := strings.Split(out, "\n") + deviceNumbers := strings.Split(out.String(), "\n") if !lxd.PathExists(v.getUEFIDevFile()) { fields := strings.Split(deviceNumbers[1], ":") @@ -236,12 +239,14 @@ func (v *vm) getRootfsPartitionUUID() (string, error) { return "", errors.New("Disk image not mounted") } - stdout, err := lxd.RunCommand("blkid", "-s", "PARTUUID", "-o", "value", v.getRootfsDevFile()) + var out strings.Builder + + err := shared.RunCommand(v.ctx, nil, &out, "blkid", "-s", "PARTUUID", "-o", "value", v.getRootfsDevFile()) if err != nil { return "", err } - return strings.TrimSpace(stdout), nil + return strings.TrimSpace(out.String()), nil } func (v *vm) getUEFIPartitionUUID() (string, error) { @@ -249,12 +254,14 @@ func (v *vm) getUEFIPartitionUUID() (string, error) { return "", errors.New("Disk image not mounted") } - stdout, err := lxd.RunCommand("blkid", "-s", "PARTUUID", "-o", "value", v.getUEFIDevFile()) + var out strings.Builder + + err := shared.RunCommand(v.ctx, nil, &out, "blkid", "-s", "PARTUUID", "-o", "value", v.getUEFIDevFile()) if err != nil { return "", err } - return strings.TrimSpace(stdout), nil + return strings.TrimSpace(out.String()), nil } func (v *vm) mountRootPartition() error { diff --git a/managers/apt.go b/managers/apt.go index 738ad64a..7a10983e 100644 --- a/managers/apt.go +++ b/managers/apt.go @@ -121,14 +121,14 @@ func (m *apt) manageRepository(repoAction shared.DefinitionPackagesRepository) e reader = strings.NewReader(repoAction.Key) } else { // If only key ID is provided, we need gpg to be installed early. - _, err := lxd.RunCommand("gpg", "--recv-keys", repoAction.Key) + err := shared.RunCommand(m.ctx, nil, nil, "gpg", "--recv-keys", repoAction.Key) if err != nil { return fmt.Errorf("Failed to receive GPG keys: %w", err) } var buf bytes.Buffer - err = lxd.RunCommandWithFds(nil, &buf, "gpg", "--export", "--armor", repoAction.Key) + err = shared.RunCommand(m.ctx, nil, &buf, "gpg", "--export", "--armor", repoAction.Key) if err != nil { return fmt.Errorf("Failed to export GPG keys: %w", err) } diff --git a/managers/yum.go b/managers/yum.go index 5021f142..d66cba15 100644 --- a/managers/yum.go +++ b/managers/yum.go @@ -21,7 +21,7 @@ func (m *yum) load() error { var buf bytes.Buffer globalFlags := []string{"-y"} - lxd.RunCommandWithFds(nil, &buf, "yum", "--help") + shared.RunCommand(m.ctx, nil, &buf, "yum", "--help") scanner := bufio.NewScanner(&buf) diff --git a/sources/busybox.go b/sources/busybox.go index 157fcac9..19eb61f7 100644 --- a/sources/busybox.go +++ b/sources/busybox.go @@ -77,7 +77,7 @@ mv ${source_dir}/busybox "${rootfs_dir}/bin/busybox" var buf bytes.Buffer - err = lxd.RunCommandWithFds(os.Stdin, &buf, filepath.Join(s.rootfsDir, "bin", "busybox"), "--list-full") + err = shared.RunCommand(s.ctx, os.Stdin, &buf, filepath.Join(s.rootfsDir, "bin", "busybox"), "--list-full") if err != nil { return fmt.Errorf("Failed to install busybox: %w", err) } diff --git a/sources/common.go b/sources/common.go index f4d263de..63b3d60c 100644 --- a/sources/common.go +++ b/sources/common.go @@ -188,17 +188,19 @@ func (s *common) VerifyFile(signedFile, signatureFile string) (bool, error) { gpgDir := path.Dir(keyring) defer os.RemoveAll(gpgDir) + var out strings.Builder + if signatureFile != "" { - out, err := lxd.RunCommand("gpg", "--homedir", gpgDir, "--keyring", keyring, + err := shared.RunCommand(s.ctx, nil, &out, "gpg", "--homedir", gpgDir, "--keyring", keyring, "--verify", signatureFile, signedFile) if err != nil { - return false, fmt.Errorf("Failed to verify: %s: %w", out, err) + return false, fmt.Errorf("Failed to verify: %s: %w", out.String(), err) } } else { - out, err := lxd.RunCommand("gpg", "--homedir", gpgDir, "--keyring", keyring, + err := shared.RunCommand(s.ctx, nil, &out, "gpg", "--homedir", gpgDir, "--keyring", keyring, "--verify", signedFile) if err != nil { - return false, fmt.Errorf("Failed to verify: %s: %w", out, err) + return false, fmt.Errorf("Failed to verify: %s: %w", out.String(), err) } } @@ -237,12 +239,14 @@ func (s *common) CreateGPGKeyring() (string, error) { return "", err } + var out strings.Builder + // Export keys to support gpg1 and gpg2 - out, err := lxd.RunCommand("gpg", "--homedir", gpgDir, "--export", "--output", + err = shared.RunCommand(s.ctx, nil, &out, "gpg", "--homedir", gpgDir, "--export", "--output", filepath.Join(gpgDir, "distrobuilder.gpg")) if err != nil { os.RemoveAll(gpgDir) - return "", fmt.Errorf("Failed to export keyring: %s: %w", out, err) + return "", fmt.Errorf("Failed to export keyring: %s: %w", out.String(), err) } return filepath.Join(gpgDir, "distrobuilder.gpg"), nil diff --git a/sources/rhel-common.go b/sources/rhel-common.go index 1ddc7983..de44e7ae 100644 --- a/sources/rhel-common.go +++ b/sources/rhel-common.go @@ -209,7 +209,7 @@ func (c *commonRHEL) unpackRaw(filePath, rootfsDir string, scriptRunner func() e // Figure out the offset var buf bytes.Buffer - err = lxd.RunCommandWithFds(nil, &buf, "fdisk", "-l", "-o", "Start", rawFilePath) + err = shared.RunCommand(c.ctx, nil, &buf, "fdisk", "-l", "-o", "Start", rawFilePath) if err != nil { return fmt.Errorf(`Failed to run "fdisk": %w`, err) } diff --git a/sources/ubuntu-http.go b/sources/ubuntu-http.go index 0dba4e6c..bb3ed550 100644 --- a/sources/ubuntu-http.go +++ b/sources/ubuntu-http.go @@ -58,12 +58,14 @@ func (s *ubuntu) runCoreVariant(definition shared.Definition, rootfsDir string) s.fname = strings.TrimSuffix(s.fname, ".xz") f := filepath.Join(s.fpath, s.fname) - output, err := lxd.RunCommand("fdisk", "-l", "-o", "Start", f) + var out strings.Builder + + err := shared.RunCommand(s.ctx, nil, &out, "fdisk", "-l", "-o", "Start", f) if err != nil { return fmt.Errorf(`Failed to run "fdisk": %w`, err) } - lines := strings.Split(output, "\n") + lines := strings.Split(out.String(), "\n") offset, err := strconv.Atoi(lines[len(lines)-2]) if err != nil { From d8f59b1b5a308c21abf43d7eb99f2de05c7f70b6 Mon Sep 17 00:00:00 2001 From: Thomas Hipp Date: Wed, 17 Nov 2021 16:43:44 +0100 Subject: [PATCH 5/5] sources: Add context to recvGPGKeys Signed-off-by: Thomas Hipp --- sources/common.go | 2 +- sources/common_test.go | 3 +++ sources/utils.go | 17 ++++++++++++----- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/sources/common.go b/sources/common.go index 63b3d60c..e728af6c 100644 --- a/sources/common.go +++ b/sources/common.go @@ -227,7 +227,7 @@ func (s *common) CreateGPGKeyring() (string, error) { var ok bool for i := 0; i < 3; i++ { - ok, err = recvGPGKeys(gpgDir, s.definition.Source.Keyserver, s.definition.Source.Keys) + ok, err = recvGPGKeys(s.ctx, gpgDir, s.definition.Source.Keyserver, s.definition.Source.Keys) if ok { break } diff --git a/sources/common_test.go b/sources/common_test.go index bcd54f42..b371b0ce 100644 --- a/sources/common_test.go +++ b/sources/common_test.go @@ -1,6 +1,7 @@ package sources import ( + "context" "log" "os" "path" @@ -85,6 +86,7 @@ func TestVerifyFile(t *testing.T) { definition: shared.Definition{ Source: shared.DefinitionSource{}, }, + ctx: context.TODO(), } for i, tt := range tests { @@ -117,6 +119,7 @@ func TestCreateGPGKeyring(t *testing.T) { Keys: []string{"0x5DE8949A899C8D99"}, }, }, + ctx: context.TODO(), } keyring, err := c.CreateGPGKeyring() diff --git a/sources/utils.go b/sources/utils.go index e5c3980f..3c58c3e1 100644 --- a/sources/utils.go +++ b/sources/utils.go @@ -3,6 +3,7 @@ package sources import ( "bufio" "bytes" + "context" "errors" "fmt" "hash" @@ -129,7 +130,7 @@ func getChecksum(fname string, hashLen int, r io.Reader) []string { return nil } -func recvGPGKeys(gpgDir string, keyserver string, keys []string) (bool, error) { +func recvGPGKeys(ctx context.Context, gpgDir string, keyserver string, keys []string) (bool, error) { args := []string{"--homedir", gpgDir} var fingerprints []string @@ -146,7 +147,7 @@ func recvGPGKeys(gpgDir string, keyserver string, keys []string) (bool, error) { for _, f := range publicKeys { args := append(args, "--import") - cmd := exec.Command("gpg", args...) + cmd := exec.CommandContext(ctx, "gpg", args...) cmd.Stdin = strings.NewReader(f) cmd.Env = append(os.Environ(), "LANG=C.UTF-8") @@ -165,15 +166,21 @@ func recvGPGKeys(gpgDir string, keyserver string, keys []string) (bool, error) { args = append(args, append([]string{"--recv-keys"}, fingerprints...)...) - _, out, err := lxd.RunCommandSplit(append(os.Environ(), "LANG=C.UTF-8"), nil, "gpg", args...) + cmd := exec.CommandContext(ctx, "gpg", args...) + cmd.Env = append(os.Environ(), "LANG=C.UTF-8") + + var buffer bytes.Buffer + cmd.Stderr = &buffer + + err := cmd.Run() if err != nil { - return false, err + return false, fmt.Errorf("Failed to run: %s: %s", strings.Join(cmd.Args, " "), strings.TrimSpace(buffer.String())) } // Verify output var importedKeys []string var missingKeys []string - lines := strings.Split(out, "\n") + lines := strings.Split(buffer.String(), "\n") for _, l := range lines { if strings.HasPrefix(l, "gpg: key ") && (strings.HasSuffix(l, " imported") || strings.HasSuffix(l, " not changed")) {