diff --git a/cli-plugins/examples/helloworld/main.go b/cli-plugins/examples/helloworld/main.go index e501fc37d4..cfbae368b0 100644 --- a/cli-plugins/examples/helloworld/main.go +++ b/cli-plugins/examples/helloworld/main.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "os" "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli-plugins/plugin" @@ -33,6 +34,16 @@ func main() { }, } + exitStatus2 := &cobra.Command{ + Use: "exitstatus2", + Short: "Exit with status 2", + RunE: func(_ *cobra.Command, _ []string) error { + fmt.Fprintln(dockerCli.Err(), "Exiting with error status 2") + os.Exit(2) + return nil + }, + } + var who string cmd := &cobra.Command{ Use: "helloworld", @@ -54,10 +65,11 @@ func main() { return dockerCli.ConfigFile().Save() }, } + flags := cmd.Flags() flags.StringVar(&who, "who", "", "Who are we addressing?") - cmd.AddCommand(goodbye, apiversion) + cmd.AddCommand(goodbye, apiversion, exitStatus2) return cmd }, manager.Metadata{ diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index d55f02c49b..dc540430d9 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "os" + "os/exec" "strings" + "syscall" "github.com/docker/cli/cli" pluginmanager "github.com/docker/cli/cli-plugins/manager" @@ -55,7 +57,21 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command { return err } - return plugincmd.Run() + err = plugincmd.Run() + if err != nil { + statusCode := 1 + exitErr, ok := err.(*exec.ExitError) + if !ok { + return err + } + if ws, ok := exitErr.Sys().(syscall.WaitStatus); ok { + statusCode = ws.ExitStatus() + } + return cli.StatusError{ + StatusCode: statusCode, + } + } + return nil }, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { // flags must be the top-level command flags, not cmd.Flags() diff --git a/e2e/cli-plugins/run_test.go b/e2e/cli-plugins/run_test.go index 22fa474a69..8e6b49ca15 100644 --- a/e2e/cli-plugins/run_test.go +++ b/e2e/cli-plugins/run_test.go @@ -194,3 +194,16 @@ func TestCliInitialized(t *testing.T) { assert.Assert(t, res.Stdout() != "") assert.Assert(t, is.Equal(res.Stderr(), "")) } + +// TestPluginErrorCode tests when the plugin return with a given exit status. +// We want to verify that the exit status does not get output to stdout and also that we return the exit code. +func TestPluginErrorCode(t *testing.T) { + run, _, cleanup := prepare(t) + defer cleanup() + res := icmd.RunCmd(run("helloworld", "exitstatus2")) + res.Assert(t, icmd.Expected{ + ExitCode: 2, + Err: "Exiting with error status 2", + }) + assert.Assert(t, is.Equal(res.Stdout(), "")) +} diff --git a/e2e/cli-plugins/testdata/docker-help-helloworld.golden b/e2e/cli-plugins/testdata/docker-help-helloworld.golden index 089bee2caf..59959637cb 100644 --- a/e2e/cli-plugins/testdata/docker-help-helloworld.golden +++ b/e2e/cli-plugins/testdata/docker-help-helloworld.golden @@ -8,6 +8,7 @@ Options: Commands: apiversion Print the API version of the server + exitstatus2 Exit with status 2 goodbye Say Goodbye instead of Hello Run 'docker helloworld COMMAND --help' for more information on a command.