From f9c3ab40c7c72f121567559d0694f11cb3bb8973 Mon Sep 17 00:00:00 2001 From: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:00:29 +0100 Subject: [PATCH] cli/command: ctx cancel should not print or produce a non zero exit code The user might kill the CLI through a SIGINT/SIGTERM which cancels the main context we pass around. Currently the context cancel error is printed alongside any other wrapped error with a generic exit code (125). This patch improves on this behavior and prevents any error from being printed when they match `context.Cancelled`. The `cli.StatusError` error would wrap errors but not provide a way to unwrap them. This would lead to situations where `errors.Is` would not match the underlying error. Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com> --- cli/cobra.go | 5 +++-- cli/command/config/inspect.go | 3 ++- cli/command/container/attach.go | 3 ++- cli/command/container/attach_test.go | 4 ++-- cli/command/container/create.go | 13 ++++++------ cli/command/container/create_test.go | 4 ++-- cli/command/container/exec.go | 5 +++-- cli/command/container/exec_test.go | 4 ++-- cli/command/container/run.go | 31 ++++++++++++++-------------- cli/command/container/run_test.go | 18 ++++++++-------- cli/command/container/start.go | 3 ++- cli/command/image/build.go | 3 ++- cli/command/inspect/inspector.go | 8 +++---- cli/command/network/remove.go | 3 ++- cli/command/node/inspect.go | 3 ++- cli/command/secret/inspect.go | 3 ++- cli/command/service/inspect.go | 3 ++- cli/command/system/events.go | 5 +++-- cli/command/system/info.go | 5 +++-- cli/command/system/version.go | 3 ++- cmd/docker/docker.go | 7 ++++--- internal/error.go | 26 +++++++++++++++++++++++ 22 files changed, 102 insertions(+), 60 deletions(-) create mode 100644 internal/error.go diff --git a/cli/cobra.go b/cli/cobra.go index feab2b4a91c8..6c6982a318eb 100644 --- a/cli/cobra.go +++ b/cli/cobra.go @@ -10,6 +10,7 @@ import ( pluginmanager "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli/command" cliflags "github.com/docker/cli/cli/flags" + "github.com/docker/cli/internal" "github.com/docker/docker/pkg/homedir" "github.com/docker/docker/registry" "github.com/fvbommel/sortorder" @@ -92,8 +93,8 @@ func FlagErrorFunc(cmd *cobra.Command, err error) error { return nil } - return StatusError{ - Status: fmt.Sprintf("%s\n\nUsage: %s\n\nRun '%s --help' for more information", err, cmd.UseLine(), cmd.CommandPath()), + return internal.StatusError{ + Cause: fmt.Errorf("%w\n\nUsage: %s\n\nRun '%s --help' for more information", err, cmd.UseLine(), cmd.CommandPath()), StatusCode: 125, } } diff --git a/cli/command/config/inspect.go b/cli/command/config/inspect.go index 7ae4a4c40435..aa43298dab86 100644 --- a/cli/command/config/inspect.go +++ b/cli/command/config/inspect.go @@ -12,6 +12,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" + "github.com/docker/cli/internal" "github.com/spf13/cobra" ) @@ -67,7 +68,7 @@ func RunConfigInspect(ctx context.Context, dockerCli command.Cli, opts InspectOp } if err := InspectFormatWrite(configCtx, opts.Names, getRef); err != nil { - return cli.StatusError{StatusCode: 1, Status: err.Error()} + return internal.StatusError{StatusCode: 1, Cause: err} } return nil } diff --git a/cli/command/container/attach.go b/cli/command/container/attach.go index a4dbe0ec0d11..ddbaa084a9b6 100644 --- a/cli/command/container/attach.go +++ b/cli/command/container/attach.go @@ -7,6 +7,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" + "github.com/docker/cli/internal" "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" "github.com/moby/sys/signal" @@ -161,7 +162,7 @@ func getExitStatus(errC <-chan error, resultC <-chan container.WaitResponse) err return errors.New(result.Error.Message) } if result.StatusCode != 0 { - return cli.StatusError{StatusCode: int(result.StatusCode)} + return internal.StatusError{StatusCode: int(result.StatusCode)} } case err := <-errC: return err diff --git a/cli/command/container/attach_test.go b/cli/command/container/attach_test.go index 5b0276f38ccf..3212d48a0829 100644 --- a/cli/command/container/attach_test.go +++ b/cli/command/container/attach_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "github.com/docker/cli/cli" + "github.com/docker/cli/internal" "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/container" "github.com/pkg/errors" @@ -110,7 +110,7 @@ func TestGetExitStatus(t *testing.T) { result: &container.WaitResponse{ StatusCode: 15, }, - expectedError: cli.StatusError{StatusCode: 15}, + expectedError: internal.StatusError{StatusCode: 15}, }, } diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 9f73b7f0afe3..81e7e3851371 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -14,6 +14,7 @@ import ( "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/streams" + "github.com/docker/cli/internal" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/container" imagetypes "github.com/docker/docker/api/types/image" @@ -92,8 +93,8 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command { func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error { if err := validatePullOpt(options.pull); err != nil { - return cli.StatusError{ - Status: withHelp(err, "create").Error(), + return internal.StatusError{ + Cause: withHelp(err, "create"), StatusCode: 125, } } @@ -109,14 +110,14 @@ func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, copts.env = *opts.NewListOptsRef(&newEnv, nil) containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType) if err != nil { - return cli.StatusError{ - Status: withHelp(err, "create").Error(), + return internal.StatusError{ + Cause: withHelp(err, "create"), StatusCode: 125, } } if err = validateAPIVersion(containerCfg, dockerCli.Client().ClientVersion()); err != nil { - return cli.StatusError{ - Status: withHelp(err, "create").Error(), + return internal.StatusError{ + Cause: withHelp(err, "create"), StatusCode: 125, } } diff --git a/cli/command/container/create_test.go b/cli/command/container/create_test.go index c02ed14fb5b9..56844215f4bd 100644 --- a/cli/command/container/create_test.go +++ b/cli/command/container/create_test.go @@ -10,8 +10,8 @@ import ( "strings" "testing" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/internal" "github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test/notary" "github.com/docker/docker/api/types/container" @@ -185,7 +185,7 @@ func TestCreateContainerImagePullPolicyInvalid(t *testing.T) { &containerOptions{}, ) - statusErr := cli.StatusError{} + statusErr := internal.StatusError{} assert.Check(t, errors.As(err, &statusErr)) assert.Check(t, is.Equal(statusErr.StatusCode, 125)) assert.Check(t, is.ErrorContains(err, tc.ExpectedErrMsg)) diff --git a/cli/command/container/exec.go b/cli/command/container/exec.go index c2a1d447998b..759c039789fe 100644 --- a/cli/command/container/exec.go +++ b/cli/command/container/exec.go @@ -9,6 +9,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/internal" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" @@ -206,11 +207,11 @@ func getExecExitStatus(ctx context.Context, apiClient client.ContainerAPIClient, if !client.IsErrConnectionFailed(err) { return err } - return cli.StatusError{StatusCode: -1} + return internal.StatusError{StatusCode: -1} } status := resp.ExitCode if status != 0 { - return cli.StatusError{StatusCode: status} + return internal.StatusError{StatusCode: status} } return nil } diff --git a/cli/command/container/exec_test.go b/cli/command/container/exec_test.go index 89193b29f487..e9e7a8c695d3 100644 --- a/cli/command/container/exec_test.go +++ b/cli/command/container/exec_test.go @@ -6,8 +6,8 @@ import ( "os" "testing" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/internal" "github.com/docker/cli/internal/test" "github.com/docker/cli/opts" "github.com/docker/docker/api/types" @@ -231,7 +231,7 @@ func TestGetExecExitStatus(t *testing.T) { }, { exitCode: 15, - expectedError: cli.StatusError{StatusCode: 15}, + expectedError: internal.StatusError{StatusCode: 15}, }, } diff --git a/cli/command/container/run.go b/cli/command/container/run.go index 5ef3e8b7bf7b..4429672db7a6 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -10,6 +10,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" + "github.com/docker/cli/internal" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/container" "github.com/moby/sys/signal" @@ -84,8 +85,8 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error { if err := validatePullOpt(ropts.pull); err != nil { - return cli.StatusError{ - Status: withHelp(err, "run").Error(), + return internal.StatusError{ + Cause: withHelp(err, "run"), StatusCode: 125, } } @@ -102,14 +103,14 @@ func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ro containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType) // just in case the parse does not exit if err != nil { - return cli.StatusError{ - Status: withHelp(err, "run").Error(), + return internal.StatusError{ + Cause: withHelp(err, "run"), StatusCode: 125, } } if err = validateAPIVersion(containerCfg, dockerCli.CurrentVersion()); err != nil { - return cli.StatusError{ - Status: withHelp(err, "run").Error(), + return internal.StatusError{ + Cause: withHelp(err, "run"), StatusCode: 125, } } @@ -241,7 +242,7 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption } status := <-statusChan if status != 0 { - return cli.StatusError{StatusCode: status} + return internal.StatusError{StatusCode: status} } case status := <-statusChan: // notify hijackedIOStreamer that we're exiting and wait @@ -249,7 +250,7 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption cancelFun() <-errCh if status != 0 { - return cli.StatusError{StatusCode: status} + return internal.StatusError{StatusCode: status} } } @@ -311,7 +312,7 @@ func withHelp(err error, commandName string) error { // toStatusError attempts to detect specific error-conditions to assign // an appropriate exit-code for situations where the problem originates -// from the container. It returns [cli.StatusError] with the original +// from the container. It returns [internal.StatusError] with the original // error message and the Status field set as follows: // // - 125: for generic failures sent back from the daemon @@ -323,21 +324,21 @@ func toStatusError(err error) error { errMsg := err.Error() if strings.Contains(errMsg, "executable file not found") || strings.Contains(errMsg, "no such file or directory") || strings.Contains(errMsg, "system cannot find the file specified") { - return cli.StatusError{ - Status: withHelp(err, "run").Error(), + return internal.StatusError{ + Cause: withHelp(err, "run"), StatusCode: 127, } } if strings.Contains(errMsg, syscall.EACCES.Error()) || strings.Contains(errMsg, syscall.EISDIR.Error()) { - return cli.StatusError{ - Status: withHelp(err, "run").Error(), + return internal.StatusError{ + Cause: withHelp(err, "run"), StatusCode: 126, } } - return cli.StatusError{ - Status: withHelp(err, "run").Error(), + return internal.StatusError{ + Cause: withHelp(err, "run"), StatusCode: 125, } } diff --git a/cli/command/container/run_test.go b/cli/command/container/run_test.go index 81b176d904c9..5f42f76dd258 100644 --- a/cli/command/container/run_test.go +++ b/cli/command/container/run_test.go @@ -12,8 +12,8 @@ import ( "time" "github.com/creack/pty" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/streams" + "github.com/docker/cli/internal" "github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test/notary" "github.com/docker/docker/api/types" @@ -131,7 +131,7 @@ func TestRunAttach(t *testing.T) { select { case cmdErr := <-cmdErrC: - assert.Equal(t, cmdErr, cli.StatusError{ + assert.Equal(t, cmdErr, internal.StatusError{ StatusCode: 33, }) case <-time.After(2 * time.Second): @@ -213,7 +213,7 @@ func TestRunAttachTermination(t *testing.T) { select { case cmdErr := <-cmdErrC: - assert.Equal(t, cmdErr, cli.StatusError{ + assert.Equal(t, cmdErr, internal.StatusError{ StatusCode: 130, }) case <-time.After(2 * time.Second): @@ -294,10 +294,10 @@ func TestRunPullTermination(t *testing.T) { select { case cmdErr := <-cmdErrC: - assert.Equal(t, cmdErr, cli.StatusError{ - StatusCode: 125, - Status: "docker: context canceled\n\nRun 'docker run --help' for more information", - }) + assert.ErrorIs(t, cmdErr, context.Canceled) + v, ok := cmdErr.(internal.StatusError) + assert.Check(t, ok) + assert.Check(t, is.Equal(v.StatusCode, 125)) case <-time.After(10 * time.Second): t.Fatal("cmd did not return before the timeout") } @@ -347,7 +347,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) { cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) err := cmd.Execute() - statusErr := cli.StatusError{} + statusErr := internal.StatusError{} assert.Check(t, errors.As(err, &statusErr)) assert.Check(t, is.Equal(statusErr.StatusCode, 125)) assert.Check(t, is.ErrorContains(err, tc.expectedError)) @@ -380,7 +380,7 @@ func TestRunContainerImagePullPolicyInvalid(t *testing.T) { &containerOptions{}, ) - statusErr := cli.StatusError{} + statusErr := internal.StatusError{} assert.Check(t, errors.As(err, &statusErr)) assert.Check(t, is.Equal(statusErr.StatusCode, 125)) assert.Check(t, is.ErrorContains(err, tc.ExpectedErrMsg)) diff --git a/cli/command/container/start.go b/cli/command/container/start.go index 29aaa988d6b8..d9a58503fbd8 100644 --- a/cli/command/container/start.go +++ b/cli/command/container/start.go @@ -9,6 +9,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" + "github.com/docker/cli/internal" "github.com/docker/docker/api/types/container" "github.com/moby/sys/signal" "github.com/moby/term" @@ -172,7 +173,7 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er } if status := <-statusChan; status != 0 { - return cli.StatusError{StatusCode: status} + return internal.StatusError{StatusCode: status} } return nil case opts.Checkpoint != "": diff --git a/cli/command/image/build.go b/cli/command/image/build.go index 4a258646dcce..75906d2c24c5 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -20,6 +20,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/image/build" + "github.com/docker/cli/internal" "github.com/docker/cli/opts" "github.com/docker/docker/api" "github.com/docker/docker/api/types" @@ -371,7 +372,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) if options.quiet { fmt.Fprintf(dockerCli.Err(), "%s%s", progBuff, buildBuff) } - return cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code} + return internal.StatusError{Cause: jerr, StatusCode: jerr.Code} } return err } diff --git a/cli/command/inspect/inspector.go b/cli/command/inspect/inspector.go index 46ba8750d6a4..7904abbcfe3c 100644 --- a/cli/command/inspect/inspector.go +++ b/cli/command/inspect/inspector.go @@ -10,7 +10,7 @@ import ( "strings" "text/template" - "github.com/docker/cli/cli" + "github.com/docker/cli/internal" "github.com/docker/cli/templates" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -67,7 +67,7 @@ type GetRefFunc func(ref string) (any, []byte, error) func Inspect(out io.Writer, references []string, tmplStr string, getRef GetRefFunc) error { inspector, err := NewTemplateInspectorFromString(out, tmplStr) if err != nil { - return cli.StatusError{StatusCode: 64, Status: err.Error()} + return internal.StatusError{StatusCode: 64, Cause: err} } var inspectErrs []string @@ -88,9 +88,9 @@ func Inspect(out io.Writer, references []string, tmplStr string, getRef GetRefFu } if len(inspectErrs) != 0 { - return cli.StatusError{ + return internal.StatusError{ StatusCode: 1, - Status: strings.Join(inspectErrs, "\n"), + Cause: errors.New(strings.Join(inspectErrs, "\n")), } } return nil diff --git a/cli/command/network/remove.go b/cli/command/network/remove.go index 105e629f614c..d6d2847f364e 100644 --- a/cli/command/network/remove.go +++ b/cli/command/network/remove.go @@ -7,6 +7,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" + "github.com/docker/cli/internal" "github.com/docker/docker/api/types/network" "github.com/docker/docker/errdefs" "github.com/spf13/cobra" @@ -68,7 +69,7 @@ func runRemove(ctx context.Context, dockerCli command.Cli, networks []string, op } if status != 0 { - return cli.StatusError{StatusCode: status} + return internal.StatusError{StatusCode: status} } return nil } diff --git a/cli/command/node/inspect.go b/cli/command/node/inspect.go index 270a14bd2bdd..823d78bb9403 100644 --- a/cli/command/node/inspect.go +++ b/cli/command/node/inspect.go @@ -12,6 +12,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" + "github.com/docker/cli/internal" "github.com/spf13/cobra" ) @@ -69,7 +70,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) } if err := InspectFormatWrite(nodeCtx, opts.nodeIds, getRef); err != nil { - return cli.StatusError{StatusCode: 1, Status: err.Error()} + return internal.StatusError{StatusCode: 1, Cause: err} } return nil } diff --git a/cli/command/secret/inspect.go b/cli/command/secret/inspect.go index 5559d43d383e..3a0f38af547e 100644 --- a/cli/command/secret/inspect.go +++ b/cli/command/secret/inspect.go @@ -12,6 +12,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" + "github.com/docker/cli/internal" "github.com/spf13/cobra" ) @@ -65,7 +66,7 @@ func runSecretInspect(ctx context.Context, dockerCli command.Cli, opts inspectOp } if err := InspectFormatWrite(secretCtx, opts.names, getRef); err != nil { - return cli.StatusError{StatusCode: 1, Status: err.Error()} + return internal.StatusError{StatusCode: 1, Cause: err} } return nil } diff --git a/cli/command/service/inspect.go b/cli/command/service/inspect.go index 28b957f77310..9b5f1bc0388d 100644 --- a/cli/command/service/inspect.go +++ b/cli/command/service/inspect.go @@ -11,6 +11,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" + "github.com/docker/cli/internal" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/network" "github.com/docker/docker/errdefs" @@ -94,7 +95,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions) } if err := InspectFormatWrite(serviceCtx, opts.refs, getRef, getNetwork); err != nil { - return cli.StatusError{StatusCode: 1, Status: err.Error()} + return internal.StatusError{StatusCode: 1, Cause: err} } return nil } diff --git a/cli/command/system/events.go b/cli/command/system/events.go index 9b5f965e5b3c..426bb492cbca 100644 --- a/cli/command/system/events.go +++ b/cli/command/system/events.go @@ -14,6 +14,7 @@ import ( "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" + "github.com/docker/cli/internal" "github.com/docker/cli/opts" "github.com/docker/cli/templates" "github.com/docker/docker/api/types/events" @@ -58,9 +59,9 @@ func NewEventsCommand(dockerCli command.Cli) *cobra.Command { func runEvents(ctx context.Context, dockerCli command.Cli, options *eventsOptions) error { tmpl, err := makeTemplate(options.format) if err != nil { - return cli.StatusError{ + return internal.StatusError{ StatusCode: 64, - Status: "Error parsing format: " + err.Error(), + Cause: fmt.Errorf("error parsing format: %w", err), } } ctx, cancel := context.WithCancel(ctx) diff --git a/cli/command/system/info.go b/cli/command/system/info.go index 108a65a5f1f4..518f3c9d0478 100644 --- a/cli/command/system/info.go +++ b/cli/command/system/info.go @@ -19,6 +19,7 @@ import ( "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/debug" flagsHelper "github.com/docker/cli/cli/flags" + "github.com/docker/cli/internal" "github.com/docker/cli/templates" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/system" @@ -457,9 +458,9 @@ func formatInfo(output io.Writer, info dockerInfo, format string) error { tmpl, err := templates.Parse(format) if err != nil { - return cli.StatusError{ + return internal.StatusError{ StatusCode: 64, - Status: "template parsing error: " + err.Error(), + Cause: fmt.Errorf("template parsing error: %w", err), } } err = tmpl.Execute(output, info) diff --git a/cli/command/system/version.go b/cli/command/system/version.go index f25446b06dd8..eeb56e629697 100644 --- a/cli/command/system/version.go +++ b/cli/command/system/version.go @@ -15,6 +15,7 @@ import ( "github.com/docker/cli/cli/command/formatter/tabwriter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/cli/version" + "github.com/docker/cli/internal" "github.com/docker/cli/templates" "github.com/docker/docker/api/types" "github.com/pkg/errors" @@ -148,7 +149,7 @@ func runVersion(ctx context.Context, dockerCli command.Cli, opts *versionOptions var err error tmpl, err := newVersionTemplate(opts.format) if err != nil { - return cli.StatusError{StatusCode: 64, Status: err.Error()} + return internal.StatusError{StatusCode: 64, Cause: err} } // TODO print error if kubernetes is used? diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index 0d190cbe2ad5..83ea770e0c72 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -19,6 +19,7 @@ import ( cliflags "github.com/docker/cli/cli/flags" "github.com/docker/cli/cli/version" platformsignals "github.com/docker/cli/cmd/docker/internal/signals" + "github.com/docker/cli/internal" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/errdefs" "github.com/pkg/errors" @@ -78,13 +79,13 @@ func dockerMain(ctx context.Context) error { } // getExitCode returns the exit-code to use for the given error. -// If err is a [cli.StatusError] and has a StatusCode set, it uses the +// If err is a [internal.StatusError] and has a StatusCode set, it uses the // status-code from it, otherwise it returns "1" for any error. func getExitCode(err error) int { if err == nil { return 0 } - var stErr cli.StatusError + var stErr internal.StatusError if errors.As(err, &stErr) && stErr.StatusCode != 0 { // FIXME(thaJeztah): StatusCode should never be used with a zero status-code. Check if we do this anywhere. return stErr.StatusCode } @@ -348,7 +349,7 @@ func tryPluginRun(ctx context.Context, dockerCli command.Cli, cmd *cobra.Command if ws, ok := exitErr.Sys().(syscall.WaitStatus); ok { statusCode = ws.ExitStatus() } - return cli.StatusError{ + return internal.StatusError{ StatusCode: statusCode, } } diff --git a/internal/error.go b/internal/error.go new file mode 100644 index 000000000000..aa31fcdbd27b --- /dev/null +++ b/internal/error.go @@ -0,0 +1,26 @@ +package internal + +import "strconv" + +// StatusError reports an unsuccessful exit by a command. +type StatusError struct { + Cause error + StatusCode int +} + +// Error formats the error for printing. If a custom Status is provided, +// it is returned as-is, otherwise it generates a generic error-message +// based on the StatusCode. +func (e StatusError) Error() string { + if e.Cause == nil { + return "exit status " + strconv.Itoa(e.StatusCode) + } + return e.Cause.Error() +} + +// Unwrap returns the wrapped error. +// +// This allows StatusError to be checked with errors.Is. +func (e StatusError) Unwrap() error { + return e.Cause +}