From 508b2ef0c66a7883f00015ed26707949743131d8 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Tue, 15 Aug 2023 16:37:42 +0900 Subject: [PATCH 1/7] debug: Add `buildx debug` command Signed-off-by: Kohei Tokunaga --- commands/build.go | 294 ++++++++++++++------------- commands/debug-shell.go | 80 -------- commands/debug/root.go | 96 +++++++++ commands/root.go | 7 +- docs/guides/debugging.md | 28 +-- docs/reference/buildx.md | 30 +-- docs/reference/buildx_build.md | 1 - docs/reference/buildx_debug-shell.md | 18 -- docs/reference/buildx_debug.md | 27 +++ docs/reference/buildx_debug_build.md | 52 +++++ 10 files changed, 364 insertions(+), 269 deletions(-) delete mode 100644 commands/debug-shell.go create mode 100644 commands/debug/root.go delete mode 100644 docs/reference/buildx_debug-shell.md create mode 100644 docs/reference/buildx_debug.md create mode 100644 docs/reference/buildx_debug_build.md diff --git a/commands/build.go b/commands/build.go index 278c67fda59..59950281cd1 100644 --- a/commands/build.go +++ b/commands/build.go @@ -17,6 +17,7 @@ import ( "github.com/containerd/console" "github.com/docker/buildx/build" "github.com/docker/buildx/builder" + "github.com/docker/buildx/commands/debug" "github.com/docker/buildx/controller" cbuild "github.com/docker/buildx/controller/build" "github.com/docker/buildx/controller/control" @@ -78,9 +79,6 @@ type buildOptions struct { target string ulimits *dockeropts.UlimitOpt - invoke *invokeConfig - noBuild bool - attests []string sbom string provenance string @@ -96,6 +94,8 @@ type buildOptions struct { exportLoad bool control.ControlOptions + + invokeConfig *invokeConfig } func (o *buildOptions) toControllerOptions() (*controllerapi.BuildOptions, error) { @@ -338,11 +338,10 @@ func runBasicBuild(ctx context.Context, dockerCli command.Cli, opts *controllera } func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *controllerapi.BuildOptions, options buildOptions, printer *progress.Printer) (*client.SolveResponse, error) { - if options.invoke != nil && (options.dockerfileName == "-" || options.contextPath == "-") { + if options.invokeConfig != nil && (options.dockerfileName == "-" || options.contextPath == "-") { // stdin must be usable for monitor return nil, errors.Errorf("Dockerfile or context from stdin is not supported with invoke") } - c, err := controller.NewController(ctx, options.ControlOptions, dockerCli, printer) if err != nil { return nil, err @@ -365,50 +364,39 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *contro var resp *client.SolveResponse f := ioset.NewSingleForwarder() f.SetReader(dockerCli.In()) - if !options.noBuild { - pr, pw := io.Pipe() - f.SetWriter(pw, func() io.WriteCloser { - pw.Close() // propagate EOF - logrus.Debug("propagating stdin close") - return nil - }) + pr, pw := io.Pipe() + f.SetWriter(pw, func() io.WriteCloser { + pw.Close() // propagate EOF + logrus.Debug("propagating stdin close") + return nil + }) - ref, resp, err = c.Build(ctx, *opts, pr, printer) - if err != nil { - var be *controllererrors.BuildError - if errors.As(err, &be) { - ref = be.Ref - retErr = err - // We can proceed to monitor - } else { - return nil, errors.Wrapf(err, "failed to build") - } + ref, resp, err = c.Build(ctx, *opts, pr, printer) + if err != nil { + var be *controllererrors.BuildError + if errors.As(err, &be) { + ref = be.Ref + retErr = err + // We can proceed to monitor + } else { + return nil, errors.Wrapf(err, "failed to build") } + } - if err := pw.Close(); err != nil { - logrus.Debug("failed to close stdin pipe writer") - } - if err := pr.Close(); err != nil { - logrus.Debug("failed to close stdin pipe reader") - } + if err := pw.Close(); err != nil { + logrus.Debug("failed to close stdin pipe writer") + } + if err := pr.Close(); err != nil { + logrus.Debug("failed to close stdin pipe reader") } - // post-build operations - if options.invoke != nil && options.invoke.needsMonitor(retErr) { + if options.invokeConfig != nil && options.invokeConfig.needsDebug(retErr) { pr2, pw2 := io.Pipe() f.SetWriter(pw2, func() io.WriteCloser { pw2.Close() // propagate EOF return nil }) - con := console.Current() - if err := con.SetRaw(); err != nil { - if err := c.Disconnect(ctx, ref); err != nil { - logrus.Warnf("disconnect error: %v", err) - } - return nil, errors.Errorf("failed to configure terminal: %v", err) - } - err = monitor.RunMonitor(ctx, ref, opts, options.invoke.InvokeConfig, c, pr2, os.Stdout, os.Stderr, printer) - con.Reset() + err = options.invokeConfig.runDebug(ctx, ref, opts, c, pr2, os.Stdout, os.Stderr, printer) if err := pw2.Close(); err != nil { logrus.Debug("failed to close monitor stdin pipe reader") } @@ -424,10 +412,22 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *contro return resp, retErr } -func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { - options := buildOptions{} +func newDebuggableBuild(dockerCli command.Cli, rootOpts *rootOptions) debug.DebuggableCmd { + return &debuggableBuild{dockerCli: dockerCli, rootOpts: rootOpts} +} + +type debuggableBuild struct { + dockerCli command.Cli + rootOpts *rootOptions +} + +func (b *debuggableBuild) NewDebugger(cfg *debug.DebugConfig) *cobra.Command { + return buildCmd(b.dockerCli, b.rootOpts, cfg) +} + +func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.DebugConfig) *cobra.Command { cFlags := &commonFlags{} - var invokeFlag string + options := &buildOptions{} cmd := &cobra.Command{ Use: "build [OPTIONS] PATH | URL | -", @@ -449,15 +449,15 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { options.progress = cFlags.progress cmd.Flags().VisitAll(checkWarnedFlags) - if invokeFlag != "" { - invoke, err := parseInvokeConfig(invokeFlag) - if err != nil { + if debugConfig != nil && (debugConfig.InvokeFlag != "" || debugConfig.OnFlag != "") { + iConfig := new(invokeConfig) + if err := iConfig.parseInvokeConfig(debugConfig.InvokeFlag, debugConfig.OnFlag); err != nil { return err } - options.invoke = &invoke - options.noBuild = invokeFlag == "debug-shell" + options.invokeConfig = iConfig } - return runBuild(dockerCli, options) + + return runBuild(dockerCli, *options) }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return nil, cobra.ShellCompDirectiveFilterDirs @@ -535,8 +535,7 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--attest=type=provenance"`) if isExperimental() { - flags.StringVar(&invokeFlag, "invoke", "", "Invoke a command after the build") - flags.SetAnnotation("invoke", "experimentalCLI", nil) + // TODO: move this to debug command if needed flags.StringVar(&options.Root, "root", "", "Specify root directory of server to connect") flags.SetAnnotation("root", "experimentalCLI", nil) flags.BoolVar(&options.Detach, "detach", false, "Detach buildx server (supported only on linux)") @@ -699,96 +698,6 @@ func updateLastActivity(dockerCli command.Cli, ng *store.NodeGroup) error { return txn.UpdateLastActivity(ng) } -type invokeConfig struct { - controllerapi.InvokeConfig - invokeFlag string -} - -func (cfg *invokeConfig) needsMonitor(retErr error) bool { - switch cfg.invokeFlag { - case "debug-shell": - return true - case "on-error": - return retErr != nil - default: - return cfg.invokeFlag != "" - } -} - -func parseInvokeConfig(invoke string) (cfg invokeConfig, err error) { - cfg.invokeFlag = invoke - cfg.Tty = true - cfg.NoCmd = true - switch invoke { - case "default", "debug-shell": - return cfg, nil - case "on-error": - // NOTE: we overwrite the command to run because the original one should fail on the failed step. - // TODO: make this configurable via flags or restorable from LLB. - // Discussion: https://github.com/docker/buildx/pull/1640#discussion_r1113295900 - cfg.Cmd = []string{"/bin/sh"} - cfg.NoCmd = false - return cfg, nil - } - - csvReader := csv.NewReader(strings.NewReader(invoke)) - csvReader.LazyQuotes = true - fields, err := csvReader.Read() - if err != nil { - return cfg, err - } - if len(fields) == 1 && !strings.Contains(fields[0], "=") { - cfg.Cmd = []string{fields[0]} - cfg.NoCmd = false - return cfg, nil - } - cfg.NoUser = true - cfg.NoCwd = true - for _, field := range fields { - parts := strings.SplitN(field, "=", 2) - if len(parts) != 2 { - return cfg, errors.Errorf("invalid value %s", field) - } - key := strings.ToLower(parts[0]) - value := parts[1] - switch key { - case "args": - cfg.Cmd = append(cfg.Cmd, maybeJSONArray(value)...) - cfg.NoCmd = false - case "entrypoint": - cfg.Entrypoint = append(cfg.Entrypoint, maybeJSONArray(value)...) - if cfg.Cmd == nil { - cfg.Cmd = []string{} - cfg.NoCmd = false - } - case "env": - cfg.Env = append(cfg.Env, maybeJSONArray(value)...) - case "user": - cfg.User = value - cfg.NoUser = false - case "cwd": - cfg.Cwd = value - cfg.NoCwd = false - case "tty": - cfg.Tty, err = strconv.ParseBool(value) - if err != nil { - return cfg, errors.Errorf("failed to parse tty: %v", err) - } - default: - return cfg, errors.Errorf("unknown key %q", key) - } - } - return cfg, nil -} - -func maybeJSONArray(v string) []string { - var list []string - if err := json.Unmarshal([]byte(v), &list); err == nil { - return list - } - return []string{v} -} - func listToMap(values []string, defaultEnv bool) (map[string]string, error) { result := make(map[string]string, len(values)) for _, value := range values { @@ -897,3 +806,108 @@ func printValue(printer printFunc, version string, format string, res map[string } return printer([]byte(res["result.json"]), os.Stdout) } + +type invokeConfig struct { + controllerapi.InvokeConfig + onFlag string + invokeFlag string +} + +func (cfg *invokeConfig) needsDebug(retErr error) bool { + switch cfg.onFlag { + case "always": + return true + case "error": + return retErr != nil + default: + return cfg.invokeFlag != "" + } +} + +func (cfg *invokeConfig) runDebug(ctx context.Context, ref string, options *controllerapi.BuildOptions, c control.BuildxController, stdin io.ReadCloser, stdout io.WriteCloser, stderr console.File, progress *progress.Printer) error { + con := console.Current() + if err := con.SetRaw(); err != nil { + // TODO: run disconnect in build command (on error case) + if err := c.Disconnect(ctx, ref); err != nil { + logrus.Warnf("disconnect error: %v", err) + } + return errors.Errorf("failed to configure terminal: %v", err) + } + defer con.Reset() + return monitor.RunMonitor(ctx, ref, options, cfg.InvokeConfig, c, stdin, stdout, stderr, progress) +} + +func (cfg *invokeConfig) parseInvokeConfig(invoke, on string) error { + cfg.onFlag = on + cfg.invokeFlag = invoke + cfg.Tty = true + cfg.NoCmd = true + switch invoke { + case "default", "": + return nil + case "on-error": + // NOTE: we overwrite the command to run because the original one should fail on the failed step. + // TODO: make this configurable via flags or restorable from LLB. + // Discussion: https://github.com/docker/buildx/pull/1640#discussion_r1113295900 + cfg.Cmd = []string{"/bin/sh"} + cfg.NoCmd = false + return nil + } + + csvReader := csv.NewReader(strings.NewReader(invoke)) + csvReader.LazyQuotes = true + fields, err := csvReader.Read() + if err != nil { + return err + } + if len(fields) == 1 && !strings.Contains(fields[0], "=") { + cfg.Cmd = []string{fields[0]} + cfg.NoCmd = false + return nil + } + cfg.NoUser = true + cfg.NoCwd = true + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + if len(parts) != 2 { + return errors.Errorf("invalid value %s", field) + } + key := strings.ToLower(parts[0]) + value := parts[1] + switch key { + case "args": + cfg.Cmd = append(cfg.Cmd, maybeJSONArray(value)...) + cfg.NoCmd = false + case "entrypoint": + cfg.Entrypoint = append(cfg.Entrypoint, maybeJSONArray(value)...) + if cfg.Cmd == nil { + cfg.Cmd = []string{} + cfg.NoCmd = false + } + case "env": + cfg.Env = append(cfg.Env, maybeJSONArray(value)...) + case "user": + cfg.User = value + cfg.NoUser = false + case "cwd": + cfg.Cwd = value + cfg.NoCwd = false + case "tty": + cfg.Tty, err = strconv.ParseBool(value) + if err != nil { + return errors.Errorf("failed to parse tty: %v", err) + } + default: + return errors.Errorf("unknown key %q", key) + } + } + return nil +} + +func maybeJSONArray(v string) []string { + var list []string + if err := json.Unmarshal([]byte(v), &list); err == nil { + return list + } + return []string{v} +} diff --git a/commands/debug-shell.go b/commands/debug-shell.go deleted file mode 100644 index d198065928a..00000000000 --- a/commands/debug-shell.go +++ /dev/null @@ -1,80 +0,0 @@ -package commands - -import ( - "context" - "os" - "runtime" - - "github.com/containerd/console" - "github.com/docker/buildx/controller" - "github.com/docker/buildx/controller/control" - controllerapi "github.com/docker/buildx/controller/pb" - "github.com/docker/buildx/monitor" - "github.com/docker/buildx/util/progress" - "github.com/docker/cli/cli/command" - "github.com/moby/buildkit/util/progress/progressui" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -func debugShellCmd(dockerCli command.Cli) *cobra.Command { - var options control.ControlOptions - var progressMode string - - cmd := &cobra.Command{ - Use: "debug-shell", - Short: "Start a monitor", - Annotations: map[string]string{ - "experimentalCLI": "", - }, - RunE: func(cmd *cobra.Command, args []string) error { - printer, err := progress.NewPrinter(context.TODO(), os.Stderr, progressui.DisplayMode(progressMode)) - if err != nil { - return err - } - - ctx := context.TODO() - c, err := controller.NewController(ctx, options, dockerCli, printer) - if err != nil { - return err - } - defer func() { - if err := c.Close(); err != nil { - logrus.Warnf("failed to close server connection %v", err) - } - }() - con := console.Current() - if err := con.SetRaw(); err != nil { - return errors.Errorf("failed to configure terminal: %v", err) - } - - err = monitor.RunMonitor(ctx, "", nil, controllerapi.InvokeConfig{ - Tty: true, - }, c, dockerCli.In(), os.Stdout, os.Stderr, printer) - con.Reset() - return err - }, - } - - flags := cmd.Flags() - - flags.StringVar(&options.Root, "root", "", "Specify root directory of server to connect") - flags.SetAnnotation("root", "experimentalCLI", nil) - - flags.BoolVar(&options.Detach, "detach", runtime.GOOS == "linux", "Detach buildx server (supported only on linux)") - flags.SetAnnotation("detach", "experimentalCLI", nil) - - flags.StringVar(&options.ServerConfig, "server-config", "", "Specify buildx server config file (used only when launching new server)") - flags.SetAnnotation("server-config", "experimentalCLI", nil) - - flags.StringVar(&progressMode, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`) - - return cmd -} - -func addDebugShellCommand(cmd *cobra.Command, dockerCli command.Cli) { - cmd.AddCommand( - debugShellCmd(dockerCli), - ) -} diff --git a/commands/debug/root.go b/commands/debug/root.go new file mode 100644 index 00000000000..2253b4b5187 --- /dev/null +++ b/commands/debug/root.go @@ -0,0 +1,96 @@ +package debug + +import ( + "context" + "os" + "runtime" + + "github.com/containerd/console" + "github.com/docker/buildx/controller" + "github.com/docker/buildx/controller/control" + controllerapi "github.com/docker/buildx/controller/pb" + "github.com/docker/buildx/monitor" + "github.com/docker/buildx/util/progress" + "github.com/docker/cli/cli/command" + "github.com/moby/buildkit/util/progress/progressui" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +// DebugConfig is a user-specified configuration for the debugger. +type DebugConfig struct { + // InvokeFlag is a flag to configure the launched debugger and the commaned executed on the debugger. + InvokeFlag string + + // OnFlag is a flag to configure the timing of launching the debugger. + OnFlag string +} + +// DebuggableCmd is a command that supports debugger with recognizing the user-specified DebugConfig. +type DebuggableCmd interface { + // NewDebugger returns the new *cobra.Command with support for the debugger with recognizing DebugConfig. + NewDebugger(*DebugConfig) *cobra.Command +} + +func RootCmd(dockerCli command.Cli, children ...DebuggableCmd) *cobra.Command { + var controlOptions control.ControlOptions + var progressMode string + var options DebugConfig + + cmd := &cobra.Command{ + Use: "debug", + Short: "Start debugger", + Args: cobra.NoArgs, + Annotations: map[string]string{ + "experimentalCLI": "", + }, + RunE: func(cmd *cobra.Command, args []string) error { + printer, err := progress.NewPrinter(context.TODO(), os.Stderr, progressui.DisplayMode(progressMode)) + if err != nil { + return err + } + + ctx := context.TODO() + c, err := controller.NewController(ctx, controlOptions, dockerCli, printer) + if err != nil { + return err + } + defer func() { + if err := c.Close(); err != nil { + logrus.Warnf("failed to close server connection %v", err) + } + }() + con := console.Current() + if err := con.SetRaw(); err != nil { + return errors.Errorf("failed to configure terminal: %v", err) + } + + err = monitor.RunMonitor(ctx, "", nil, controllerapi.InvokeConfig{ + Tty: true, + }, c, dockerCli.In(), os.Stdout, os.Stderr, printer) + con.Reset() + return err + }, + } + + flags := cmd.Flags() + flags.StringVar(&options.InvokeFlag, "invoke", "", "Launch a monitor with executing specified command") + flags.SetAnnotation("invoke", "experimentalCLI", nil) + flags.StringVar(&options.OnFlag, "on", "", "When to launch the monitor ([always, error])") + flags.SetAnnotation("on", "experimentalCLI", nil) + + flags.StringVar(&controlOptions.Root, "root", "", "Specify root directory of server to connect for the monitor") + flags.SetAnnotation("root", "experimentalCLI", nil) + flags.BoolVar(&controlOptions.Detach, "detach", runtime.GOOS == "linux", "Detach buildx server for the monitor (supported only on linux)") + flags.SetAnnotation("detach", "experimentalCLI", nil) + flags.StringVar(&controlOptions.ServerConfig, "server-config", "", "Specify buildx server config file for the monitor (used only when launching new server)") + flags.SetAnnotation("server-config", "experimentalCLI", nil) + flags.StringVar(&progressMode, "progress", "auto", `Set type of progress output ("auto", "plain", "tty") for the monitor. Use plain to show container output`) + + for _, c := range children { + cmd.AddCommand(c.NewDebugger(&options)) + } + + return cmd +} diff --git a/commands/root.go b/commands/root.go index 500a93f46cb..43a43a4c10c 100644 --- a/commands/root.go +++ b/commands/root.go @@ -3,6 +3,7 @@ package commands import ( "os" + debugcmd "github.com/docker/buildx/commands/debug" imagetoolscmd "github.com/docker/buildx/commands/imagetools" "github.com/docker/buildx/controller/remote" "github.com/docker/buildx/util/cobrautil/completion" @@ -71,7 +72,7 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) { rootFlags(opts, cmd.PersistentFlags()) cmd.AddCommand( - buildCmd(dockerCli, opts), + buildCmd(dockerCli, opts, nil), bakeCmd(dockerCli, opts), createCmd(dockerCli), rmCmd(dockerCli, opts), @@ -87,8 +88,10 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) { imagetoolscmd.RootCmd(dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}), ) if isExperimental() { + cmd.AddCommand(debugcmd.RootCmd(dockerCli, + newDebuggableBuild(dockerCli, opts), + )) remote.AddControllerCommands(cmd, dockerCli) - addDebugShellCommand(cmd, dockerCli) } cmd.RegisterFlagCompletionFunc( //nolint:errcheck diff --git a/docs/guides/debugging.md b/docs/guides/debugging.md index 2de3666f651..d066d2a8705 100644 --- a/docs/guides/debugging.md +++ b/docs/guides/debugging.md @@ -19,11 +19,13 @@ your environment. $ export BUILDX_EXPERIMENTAL=1 ``` -To start a debug session for a build, you can use the `--invoke` flag with the -build command to specify a command to launch in the resulting image. +To start a debug session for a build, you can use the `buildx debug` command with `--invoke` flag to specify a command to launch in the resulting image. +`buildx debug` command provides `buildx debug build` subcommand that provides the same features as the normal `buildx build` command but allows launching the debugger session after the build. + +Arguments available after `buildx debug build` are the same as the normal `buildx build`. ```console -$ docker buildx build --invoke /bin/sh . +$ docker buildx debug --invoke /bin/sh build . [+] Building 4.2s (19/19) FINISHED => [internal] connecting to local controller 0.0s => [internal] load build definition from Dockerfile 0.0s @@ -56,16 +58,16 @@ Supported keys are `args` (can be JSON array format), `entrypoint` (can be JSON Example: ``` -$ docker buildx build --invoke 'entrypoint=["sh"],"args=[""-c"", ""env | grep -e FOO -e AAA""]","env=[""FOO=bar"", ""AAA=bbb""]"' . +$ docker buildx debug --invoke 'entrypoint=["sh"],"args=[""-c"", ""env | grep -e FOO -e AAA""]","env=[""FOO=bar"", ""AAA=bbb""]"' build . ``` -#### `on-error` +#### `on` flag If you want to start a debug session when a build fails, you can use -`--invoke=on-error` to start a debug session when the build fails. +`--on=error` to start a debug session when the build fails. ```console -$ docker buildx build --invoke on-error . +$ docker buildx debug --on=error build . [+] Building 4.2s (19/19) FINISHED => [internal] connecting to local controller 0.0s => [internal] load build definition from Dockerfile 0.0s @@ -85,13 +87,13 @@ Interactive container was restarted with process "edmzor60nrag7rh1mbi4o9lm8". Pr This allows you to explore the state of the image when the build failed. -#### `debug-shell` +#### Launch the debug session directly with `buildx debug` subcommand If you want to drop into a debug session without first starting the build, you -can use `--invoke=debug-shell` to start a debug session. +can use `buildx debug` command to start a debug session. ``` -$ docker buildx build --invoke debug-shell . +$ docker buildx debug [+] Building 4.2s (19/19) FINISHED => [internal] connecting to local controller 0.0s (buildx) @@ -135,15 +137,15 @@ To detach the build process from the CLI, you can use the `--detach=true` flag w the build command. ```console -$ docker buildx build --detach=true --invoke /bin/sh . +$ docker buildx debug --invoke /bin/sh build --detach=true . ``` If you start a debugging session using the `--invoke` flag with a detached -build, then you can attach to it using the `buildx debug-shell` subcommand to +build, then you can attach to it using the `buildx debug` command to immediately enter the monitor mode. ```console -$ docker buildx debug-shell +$ docker buildx debug [+] Building 0.0s (1/1) FINISHED => [internal] connecting to remote controller (buildx) list diff --git a/docs/reference/buildx.md b/docs/reference/buildx.md index 324cb013a04..c1b760dd176 100644 --- a/docs/reference/buildx.md +++ b/docs/reference/buildx.md @@ -9,21 +9,21 @@ Extended build capabilities with BuildKit ### Subcommands -| Name | Description | -|:---------------------------------------|:---------------------------------------| -| [`bake`](buildx_bake.md) | Build from a file | -| [`build`](buildx_build.md) | Start a build | -| [`create`](buildx_create.md) | Create a new builder instance | -| [`debug-shell`](buildx_debug-shell.md) | Start a monitor | -| [`du`](buildx_du.md) | Disk usage | -| [`imagetools`](buildx_imagetools.md) | Commands to work on images in registry | -| [`inspect`](buildx_inspect.md) | Inspect current builder instance | -| [`ls`](buildx_ls.md) | List builder instances | -| [`prune`](buildx_prune.md) | Remove build cache | -| [`rm`](buildx_rm.md) | Remove a builder instance | -| [`stop`](buildx_stop.md) | Stop builder instance | -| [`use`](buildx_use.md) | Set the current builder instance | -| [`version`](buildx_version.md) | Show buildx version information | +| Name | Description | +|:-------------------------------------|:---------------------------------------| +| [`bake`](buildx_bake.md) | Build from a file | +| [`build`](buildx_build.md) | Start a build | +| [`create`](buildx_create.md) | Create a new builder instance | +| [`debug`](buildx_debug.md) | Start debugger | +| [`du`](buildx_du.md) | Disk usage | +| [`imagetools`](buildx_imagetools.md) | Commands to work on images in registry | +| [`inspect`](buildx_inspect.md) | Inspect current builder instance | +| [`ls`](buildx_ls.md) | List builder instances | +| [`prune`](buildx_prune.md) | Remove build cache | +| [`rm`](buildx_rm.md) | Remove a builder instance | +| [`stop`](buildx_stop.md) | Stop builder instance | +| [`use`](buildx_use.md) | Set the current builder instance | +| [`version`](buildx_version.md) | Show buildx version information | ### Options diff --git a/docs/reference/buildx_build.md b/docs/reference/buildx_build.md index 4cb1577dc84..2dae799e28b 100644 --- a/docs/reference/buildx_build.md +++ b/docs/reference/buildx_build.md @@ -28,7 +28,6 @@ Start a build | `--detach` | | | Detach buildx server (supported only on linux) | | [`-f`](https://docs.docker.com/engine/reference/commandline/build/#file), [`--file`](https://docs.docker.com/engine/reference/commandline/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) | | `--iidfile` | `string` | | Write the image ID to the file | -| `--invoke` | `string` | | Invoke a command after the build | | `--label` | `stringArray` | | Set metadata for an image | | [`--load`](#load) | | | Shorthand for `--output=type=docker` | | [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to the file | diff --git a/docs/reference/buildx_debug-shell.md b/docs/reference/buildx_debug-shell.md deleted file mode 100644 index c06885ae36b..00000000000 --- a/docs/reference/buildx_debug-shell.md +++ /dev/null @@ -1,18 +0,0 @@ -# docker buildx debug-shell - - -Start a monitor - -### Options - -| Name | Type | Default | Description | -|:------------------|:---------|:--------|:-----------------------------------------------------------------------------------------| -| `--builder` | `string` | | Override the configured builder instance | -| `--detach` | | | Detach buildx server (supported only on linux) | -| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output | -| `--root` | `string` | | Specify root directory of server to connect | -| `--server-config` | `string` | | Specify buildx server config file (used only when launching new server) | - - - - diff --git a/docs/reference/buildx_debug.md b/docs/reference/buildx_debug.md new file mode 100644 index 00000000000..bdf5642a1fe --- /dev/null +++ b/docs/reference/buildx_debug.md @@ -0,0 +1,27 @@ +# docker buildx debug + + +Start debugger + +### Subcommands + +| Name | Description | +|:---------------------------------|:--------------| +| [`build`](buildx_debug_build.md) | Start a build | + + +### Options + +| Name | Type | Default | Description | +|:------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------------| +| `--builder` | `string` | | Override the configured builder instance | +| `--detach` | | | Detach buildx server for the monitor (supported only on linux) | +| `--invoke` | `string` | | Launch a monitor with executing specified command | +| `--on` | `string` | | When to launch the monitor ([always, error]) | +| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`) for the monitor. Use plain to show container output | +| `--root` | `string` | | Specify root directory of server to connect for the monitor | +| `--server-config` | `string` | | Specify buildx server config file for the monitor (used only when launching new server) | + + + + diff --git a/docs/reference/buildx_debug_build.md b/docs/reference/buildx_debug_build.md new file mode 100644 index 00000000000..84d31beb290 --- /dev/null +++ b/docs/reference/buildx_debug_build.md @@ -0,0 +1,52 @@ +# docker buildx debug build + + +Start a build + +### Aliases + +`docker buildx debug build`, `docker buildx debug b` + +### Options + +| Name | Type | Default | Description | +|:-------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------|:----------|:----------------------------------------------------------------------------------------------------| +| [`--add-host`](https://docs.docker.com/engine/reference/commandline/build/#add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) | +| `--allow` | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) | +| `--attest` | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) | +| `--build-arg` | `stringArray` | | Set build-time variables | +| `--build-context` | `stringArray` | | Additional build contexts (e.g., name=path) | +| `--builder` | `string` | | Override the configured builder instance | +| `--cache-from` | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) | +| `--cache-to` | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) | +| [`--cgroup-parent`](https://docs.docker.com/engine/reference/commandline/build/#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build | +| `--detach` | | | Detach buildx server (supported only on linux) | +| [`-f`](https://docs.docker.com/engine/reference/commandline/build/#file), [`--file`](https://docs.docker.com/engine/reference/commandline/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) | +| `--iidfile` | `string` | | Write the image ID to the file | +| `--label` | `stringArray` | | Set metadata for an image | +| `--load` | | | Shorthand for `--output=type=docker` | +| `--metadata-file` | `string` | | Write build result metadata to the file | +| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build | +| `--no-cache` | | | Do not use cache when building the image | +| `--no-cache-filter` | `stringArray` | | Do not cache specified stages | +| `-o`, `--output` | `stringArray` | | Output destination (format: `type=local,dest=path`) | +| `--platform` | `stringArray` | | Set target platform for build | +| `--print` | `string` | | Print result of information request (e.g., outline, targets) | +| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output | +| `--provenance` | `string` | | Shorthand for `--attest=type=provenance` | +| `--pull` | | | Always attempt to pull all referenced images | +| `--push` | | | Shorthand for `--output=type=registry` | +| `-q`, `--quiet` | | | Suppress the build output and print image ID on success | +| `--root` | `string` | | Specify root directory of server to connect | +| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` | +| `--secret` | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) | +| `--server-config` | `string` | | Specify buildx server config file (used only when launching new server) | +| `--shm-size` | `bytes` | `0` | Size of `/dev/shm` | +| `--ssh` | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|[=\|[,]]`) | +| [`-t`](https://docs.docker.com/engine/reference/commandline/build/#tag), [`--tag`](https://docs.docker.com/engine/reference/commandline/build/#tag) | `stringArray` | | Name and optionally a tag (format: `name:tag`) | +| [`--target`](https://docs.docker.com/engine/reference/commandline/build/#target) | `string` | | Set the target build stage to build | +| `--ulimit` | `ulimit` | | Ulimit options | + + + + From ded91da5758c09ed42d74155307c1079e7da746f Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Fri, 13 Oct 2023 10:14:04 +0900 Subject: [PATCH 2/7] exec, rollback: return error when no session found Signed-off-by: Kohei Tokunaga --- monitor/commands/exec.go | 3 +++ monitor/commands/rollback.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/monitor/commands/exec.go b/monitor/commands/exec.go index 1b9dd4dbb95..7a0abe288e0 100644 --- a/monitor/commands/exec.go +++ b/monitor/commands/exec.go @@ -35,6 +35,9 @@ COMMAND and ARG... will be executed in the container. } func (cm *ExecCmd) Exec(ctx context.Context, args []string) error { + if ref := cm.m.AttachedSessionID(); ref == "" { + return errors.Errorf("no attaching session") + } if len(args) < 2 { return errors.Errorf("command must be passed") } diff --git a/monitor/commands/rollback.go b/monitor/commands/rollback.go index 0ac986a4db9..d56e4161909 100644 --- a/monitor/commands/rollback.go +++ b/monitor/commands/rollback.go @@ -7,6 +7,7 @@ import ( controllerapi "github.com/docker/buildx/controller/pb" "github.com/docker/buildx/monitor/types" + "github.com/pkg/errors" ) type RollbackCmd struct { @@ -37,6 +38,9 @@ COMMAND and ARG... will be executed in the container. } func (cm *RollbackCmd) Exec(ctx context.Context, args []string) error { + if ref := cm.m.AttachedSessionID(); ref == "" { + return errors.Errorf("no attaching session") + } cfg := cm.invokeConfig if len(args) >= 2 { cmds := args[1:] From 5a0e4c1023d14be8cdc0e12723068c50f34ee0e3 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Fri, 13 Oct 2023 10:14:39 +0900 Subject: [PATCH 3/7] debug: set on=error by default Signed-off-by: Kohei Tokunaga --- commands/debug/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/debug/root.go b/commands/debug/root.go index 2253b4b5187..71b5b41796a 100644 --- a/commands/debug/root.go +++ b/commands/debug/root.go @@ -77,7 +77,7 @@ func RootCmd(dockerCli command.Cli, children ...DebuggableCmd) *cobra.Command { flags := cmd.Flags() flags.StringVar(&options.InvokeFlag, "invoke", "", "Launch a monitor with executing specified command") flags.SetAnnotation("invoke", "experimentalCLI", nil) - flags.StringVar(&options.OnFlag, "on", "", "When to launch the monitor ([always, error])") + flags.StringVar(&options.OnFlag, "on", "error", "When to launch the monitor ([always, error])") flags.SetAnnotation("on", "experimentalCLI", nil) flags.StringVar(&controlOptions.Root, "root", "", "Specify root directory of server to connect for the monitor") From 6db8569f096a89a0a2e1f637c4cd990b99c7b832 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Fri, 13 Oct 2023 10:21:05 +0900 Subject: [PATCH 4/7] process: Do not print error log when process is canceled Signed-off-by: Kohei Tokunaga --- controller/processes/processes.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/controller/processes/processes.go b/controller/processes/processes.go index c74b39dd243..c873860152f 100644 --- a/controller/processes/processes.go +++ b/controller/processes/processes.go @@ -137,7 +137,11 @@ func (m *Manager) StartProcess(pid string, resultCtx *build.ResultHandle, cfg *p go func() { var err error if err = ctr.Exec(ctx, cfg, in.Stdin, in.Stdout, in.Stderr); err != nil { - logrus.Errorf("failed to exec process: %v", err) + if errors.Is(err, context.Canceled) { + logrus.Debugf("process canceled: %v", err) + } else { + logrus.Errorf("failed to exec process: %v", err) + } } logrus.Debugf("finished process %s %v", pid, cfg.Entrypoint) m.processes.Delete(pid) From 8da8ee2aeac4ef5742ca59c672aa3eac4188badc Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Fri, 13 Oct 2023 11:19:08 +0900 Subject: [PATCH 5/7] controller: return original error to preserve stacktrace Signed-off-by: Kohei Tokunaga --- build/result.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/result.go b/build/result.go index f76aab6aaa8..b571cb66e62 100644 --- a/build/result.go +++ b/build/result.go @@ -117,7 +117,7 @@ func NewResultHandle(ctx context.Context, cc *client.Client, opt client.SolveOpt gwClient: c, gwCtx: ctx, } - respErr = se + respErr = err // return original error to preserve stacktrace close(done) // Block until the caller closes the ResultHandle. From 0dd89f602922851a3159976972832728aafa0bd3 Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Fri, 13 Oct 2023 11:36:14 +0900 Subject: [PATCH 6/7] monitor: print error information before launching monitor Signed-off-by: Kohei Tokunaga --- commands/build.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/commands/build.go b/commands/build.go index 59950281cd1..fb910de8029 100644 --- a/commands/build.go +++ b/commands/build.go @@ -391,6 +391,11 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *contro } if options.invokeConfig != nil && options.invokeConfig.needsDebug(retErr) { + // Print errors before launching monitor + if err := printError(retErr, printer); err != nil { + logrus.Warnf("failed to print error information: %v", err) + } + pr2, pw2 := io.Pipe() f.SetWriter(pw2, func() io.WriteCloser { pw2.Close() // propagate EOF @@ -412,6 +417,18 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *contro return resp, retErr } +func printError(err error, printer *progress.Printer) error { + if err := printer.Pause(); err != nil { + return err + } + defer printer.Unpause() + for _, s := range errdefs.Sources(err) { + s.Print(os.Stderr) + } + fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) + return nil +} + func newDebuggableBuild(dockerCli command.Cli, rootOpts *rootOptions) debug.DebuggableCmd { return &debuggableBuild{dockerCli: dockerCli, rootOpts: rootOpts} } From 198764f1162c00035462e43055cb5330cd16ad4b Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Fri, 13 Oct 2023 11:52:30 +0900 Subject: [PATCH 7/7] debug: update docs Signed-off-by: Kohei Tokunaga --- docs/reference/buildx_debug.md | 2 +- docs/reference/buildx_debug_build.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/buildx_debug.md b/docs/reference/buildx_debug.md index bdf5642a1fe..c9a85629d00 100644 --- a/docs/reference/buildx_debug.md +++ b/docs/reference/buildx_debug.md @@ -17,7 +17,7 @@ Start debugger | `--builder` | `string` | | Override the configured builder instance | | `--detach` | | | Detach buildx server for the monitor (supported only on linux) | | `--invoke` | `string` | | Launch a monitor with executing specified command | -| `--on` | `string` | | When to launch the monitor ([always, error]) | +| `--on` | `string` | `error` | When to launch the monitor ([always, error]) | | `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`) for the monitor. Use plain to show container output | | `--root` | `string` | | Specify root directory of server to connect for the monitor | | `--server-config` | `string` | | Specify buildx server config file for the monitor (used only when launching new server) | diff --git a/docs/reference/buildx_debug_build.md b/docs/reference/buildx_debug_build.md index 84d31beb290..1aa69eb8145 100644 --- a/docs/reference/buildx_debug_build.md +++ b/docs/reference/buildx_debug_build.md @@ -13,6 +13,7 @@ Start a build |:-------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------|:----------|:----------------------------------------------------------------------------------------------------| | [`--add-host`](https://docs.docker.com/engine/reference/commandline/build/#add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) | | `--allow` | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) | +| `--annotation` | `stringArray` | | Add annotation to the image | | `--attest` | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) | | `--build-arg` | `stringArray` | | Set build-time variables | | `--build-context` | `stringArray` | | Additional build contexts (e.g., name=path) |