Skip to content

Commit d5f90ed

Browse files
authored
Merge pull request #5236 from thaJeztah/cleanup_run_errors
cli/command/container: remove reportError, and put StatusError to use
2 parents 6559d86 + 90058df commit d5f90ed

File tree

4 files changed

+70
-48
lines changed

4 files changed

+70
-48
lines changed

cli/command/container/create.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,10 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
9292

9393
func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error {
9494
if err := validatePullOpt(options.pull); err != nil {
95-
reportError(dockerCli.Err(), "create", err.Error(), true)
96-
return cli.StatusError{StatusCode: 125}
95+
return cli.StatusError{
96+
Status: withHelp(err, "create").Error(),
97+
StatusCode: 125,
98+
}
9799
}
98100
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetAll()))
99101
newEnv := []string{}
@@ -107,12 +109,16 @@ func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
107109
copts.env = *opts.NewListOptsRef(&newEnv, nil)
108110
containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
109111
if err != nil {
110-
reportError(dockerCli.Err(), "create", err.Error(), true)
111-
return cli.StatusError{StatusCode: 125}
112+
return cli.StatusError{
113+
Status: withHelp(err, "create").Error(),
114+
StatusCode: 125,
115+
}
112116
}
113117
if err = validateAPIVersion(containerCfg, dockerCli.Client().ClientVersion()); err != nil {
114-
reportError(dockerCli.Err(), "create", err.Error(), true)
115-
return cli.StatusError{StatusCode: 125}
118+
return cli.StatusError{
119+
Status: withHelp(err, "create").Error(),
120+
StatusCode: 125,
121+
}
116122
}
117123
id, err := createContainer(ctx, dockerCli, containerCfg, options)
118124
if err != nil {

cli/command/container/create_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,8 @@ func TestCreateContainerImagePullPolicyInvalid(t *testing.T) {
189189

190190
statusErr := cli.StatusError{}
191191
assert.Check(t, errors.As(err, &statusErr))
192-
assert.Equal(t, statusErr.StatusCode, 125)
193-
assert.Check(t, is.Contains(dockerCli.ErrBuffer().String(), tc.ExpectedErrMsg))
192+
assert.Check(t, is.Equal(statusErr.StatusCode, 125))
193+
assert.Check(t, is.ErrorContains(err, tc.ExpectedErrMsg))
194194
})
195195
}
196196
}
@@ -285,7 +285,7 @@ func TestNewCreateCommandWithWarnings(t *testing.T) {
285285
for _, tc := range testCases {
286286
tc := tc
287287
t.Run(tc.name, func(t *testing.T) {
288-
cli := test.NewFakeCli(&fakeClient{
288+
fakeCLI := test.NewFakeCli(&fakeClient{
289289
createContainerFunc: func(config *container.Config,
290290
hostConfig *container.HostConfig,
291291
networkingConfig *network.NetworkingConfig,
@@ -295,15 +295,15 @@ func TestNewCreateCommandWithWarnings(t *testing.T) {
295295
return container.CreateResponse{}, nil
296296
},
297297
})
298-
cmd := NewCreateCommand(cli)
298+
cmd := NewCreateCommand(fakeCLI)
299299
cmd.SetOut(io.Discard)
300300
cmd.SetArgs(tc.args)
301301
err := cmd.Execute()
302302
assert.NilError(t, err)
303303
if tc.warning {
304-
golden.Assert(t, cli.ErrBuffer().String(), tc.name+".golden")
304+
golden.Assert(t, fakeCLI.ErrBuffer().String(), tc.name+".golden")
305305
} else {
306-
assert.Equal(t, cli.ErrBuffer().String(), "")
306+
assert.Equal(t, fakeCLI.ErrBuffer().String(), "")
307307
}
308308
})
309309
}

cli/command/container/run.go

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,10 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
8383

8484
func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
8585
if err := validatePullOpt(ropts.pull); err != nil {
86-
reportError(dockerCli.Err(), "run", err.Error(), true)
87-
return cli.StatusError{StatusCode: 125}
86+
return cli.StatusError{
87+
Status: withHelp(err, "run").Error(),
88+
StatusCode: 125,
89+
}
8890
}
8991
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetAll()))
9092
newEnv := []string{}
@@ -99,12 +101,16 @@ func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ro
99101
containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
100102
// just in case the parse does not exit
101103
if err != nil {
102-
reportError(dockerCli.Err(), "run", err.Error(), true)
103-
return cli.StatusError{StatusCode: 125}
104+
return cli.StatusError{
105+
Status: withHelp(err, "run").Error(),
106+
StatusCode: 125,
107+
}
104108
}
105109
if err = validateAPIVersion(containerCfg, dockerCli.CurrentVersion()); err != nil {
106-
reportError(dockerCli.Err(), "run", err.Error(), true)
107-
return cli.StatusError{StatusCode: 125}
110+
return cli.StatusError{
111+
Status: withHelp(err, "run").Error(),
112+
StatusCode: 125,
113+
}
108114
}
109115
return runContainer(ctx, dockerCli, ropts, copts, containerCfg)
110116
}
@@ -139,8 +145,7 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
139145

140146
containerID, err := createContainer(ctx, dockerCli, containerCfg, &runOpts.createOptions)
141147
if err != nil {
142-
reportError(stderr, "run", err.Error(), true)
143-
return runStartContainerErr(err)
148+
return toStatusError(err)
144149
}
145150
if runOpts.sigProxy {
146151
sigc := notifyAllSignals()
@@ -204,12 +209,11 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
204209
<-errCh
205210
}
206211

207-
reportError(stderr, "run", err.Error(), false)
208212
if copts.autoRemove {
209213
// wait container to be removed
210214
<-statusChan
211215
}
212-
return runStartContainerErr(err)
216+
return toStatusError(err)
213217
}
214218

215219
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
@@ -292,30 +296,40 @@ func attachContainer(ctx context.Context, dockerCli command.Cli, containerID str
292296
return resp.Close, nil
293297
}
294298

295-
// reportError is a utility method that prints a user-friendly message
296-
// containing the error that occurred during parsing and a suggestion to get help
297-
func reportError(stderr io.Writer, name string, str string, withHelp bool) {
298-
str = strings.TrimSuffix(str, ".") + "."
299-
if withHelp {
300-
str += "\nSee 'docker " + name + " --help'."
301-
}
302-
_, _ = fmt.Fprintln(stderr, "docker:", str)
299+
// withHelp decorates the error with a suggestion to use "--help".
300+
func withHelp(err error, commandName string) error {
301+
return fmt.Errorf("docker: %w\n\nRun 'docker %s --help' for more information", err, commandName)
303302
}
304303

305-
// if container start fails with 'not found'/'no such' error, return 127
306-
// if container start fails with 'permission denied' error, return 126
307-
// return 125 for generic docker daemon failures
308-
func runStartContainerErr(err error) error {
309-
trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ")
310-
statusError := cli.StatusError{StatusCode: 125}
311-
if strings.Contains(trimmedErr, "executable file not found") ||
312-
strings.Contains(trimmedErr, "no such file or directory") ||
313-
strings.Contains(trimmedErr, "system cannot find the file specified") {
314-
statusError = cli.StatusError{StatusCode: 127}
315-
} else if strings.Contains(trimmedErr, syscall.EACCES.Error()) ||
316-
strings.Contains(trimmedErr, syscall.EISDIR.Error()) {
317-
statusError = cli.StatusError{StatusCode: 126}
304+
// toStatusError attempts to detect specific error-conditions to assign
305+
// an appropriate exit-code for situations where the problem originates
306+
// from the container. It returns [cli.StatusError] with the original
307+
// error message and the Status field set as follows:
308+
//
309+
// - 125: for generic failures sent back from the daemon
310+
// - 126: if container start fails with 'permission denied' error
311+
// - 127: if container start fails with 'not found'/'no such' error
312+
func toStatusError(err error) error {
313+
// TODO(thaJeztah): some of these errors originate from the container: should we actually suggest "--help" for those?
314+
315+
errMsg := err.Error()
316+
317+
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") {
318+
return cli.StatusError{
319+
Status: withHelp(err, "run").Error(),
320+
StatusCode: 127,
321+
}
318322
}
319323

320-
return statusError
324+
if strings.Contains(errMsg, syscall.EACCES.Error()) || strings.Contains(errMsg, syscall.EISDIR.Error()) {
325+
return cli.StatusError{
326+
Status: withHelp(err, "run").Error(),
327+
StatusCode: 126,
328+
}
329+
}
330+
331+
return cli.StatusError{
332+
Status: withHelp(err, "run").Error(),
333+
StatusCode: 125,
334+
}
321335
}

cli/command/container/run_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,10 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
145145
cmd.SetOut(io.Discard)
146146
cmd.SetErr(io.Discard)
147147
err := cmd.Execute()
148-
assert.Assert(t, err != nil)
149-
assert.Assert(t, is.Contains(fakeCLI.ErrBuffer().String(), tc.expectedError))
148+
statusErr := cli.StatusError{}
149+
assert.Check(t, errors.As(err, &statusErr))
150+
assert.Check(t, is.Equal(statusErr.StatusCode, 125))
151+
assert.Check(t, is.ErrorContains(err, tc.expectedError))
150152
})
151153
}
152154
}
@@ -179,8 +181,8 @@ func TestRunContainerImagePullPolicyInvalid(t *testing.T) {
179181

180182
statusErr := cli.StatusError{}
181183
assert.Check(t, errors.As(err, &statusErr))
182-
assert.Equal(t, statusErr.StatusCode, 125)
183-
assert.Check(t, is.Contains(dockerCli.ErrBuffer().String(), tc.ExpectedErrMsg))
184+
assert.Check(t, is.Equal(statusErr.StatusCode, 125))
185+
assert.Check(t, is.ErrorContains(err, tc.ExpectedErrMsg))
184186
})
185187
}
186188
}

0 commit comments

Comments
 (0)