diff --git a/cli-plugins/manager/manager.go b/cli-plugins/manager/manager.go index a68167b295cb..9d5dc9aa9c39 100644 --- a/cli-plugins/manager/manager.go +++ b/cli-plugins/manager/manager.go @@ -181,3 +181,10 @@ func PluginRunCommand(name string, rootcmd *cobra.Command) (*exec.Cmd, error) { // they lack e.g. global options which we must propagate here. return runPluginCommand(name, rootcmd, os.Args[1:]) } + +// PluginHelpCommand returns an "os/exec".Cmd which when .Run() will execute the named plugin's help command. +// The rootcmd argument is referenced to determine the set of builtin commands in order to detect conficts. +// The error returned is an ErrPluginNotFound if no plugin was found or if the first candidate plugin was invalid somehow. +func PluginHelpCommand(name string, rootcmd *cobra.Command) (*exec.Cmd, error) { + return runPluginCommand(name, rootcmd, []string{"help", name}) +} diff --git a/cli/cobra.go b/cli/cobra.go index 3a8755e0d302..04c9f3b36076 100644 --- a/cli/cobra.go +++ b/cli/cobra.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "os" "strings" pluginmanager "github.com/docker/cli/cli-plugins/manager" @@ -57,6 +58,18 @@ var helpCommand = &cobra.Command{ RunE: func(c *cobra.Command, args []string) error { cmd, args, e := c.Root().Find(args) if cmd == nil || e != nil || len(args) > 0 { + if len(args) == 1 { + helpcmd, err := pluginmanager.PluginHelpCommand(args[0], cmd.Root()) + if err == nil { + helpcmd.Stdin = os.Stdin + helpcmd.Stdout = os.Stdout + helpcmd.Stderr = os.Stderr + return helpcmd.Run() + } + if _, ok := err.(pluginmanager.ErrPluginNotFound); !ok { + return err + } + } return errors.Errorf("unknown help topic: %v", strings.Join(args, " ")) } diff --git a/e2e/cli-plugins/run_test.go b/e2e/cli-plugins/run_test.go index 418210134cc6..27ba5855f7a5 100644 --- a/e2e/cli-plugins/run_test.go +++ b/e2e/cli-plugins/run_test.go @@ -14,6 +14,20 @@ func TestRunNonexisting(t *testing.T) { }) } +func TestHelpNonexisting(t *testing.T) { + res := icmd.RunCmd(icmd.Command("docker", "help", "nonexistent")) + res.Assert(t, icmd.Expected{ + ExitCode: 1, + Err: "unknown help topic: nonexistent", + }) + + res = icmd.RunCmd(icmd.Command("docker", "nonexistent", "--help")) + res.Assert(t, icmd.Expected{ + ExitCode: 0, + Out: "Usage: docker [OPTIONS] COMMAND", + }) +} + func TestRunBad(t *testing.T) { res := icmd.RunCmd(icmd.Command("docker", "badmeta")) res.Assert(t, icmd.Expected{ @@ -22,6 +36,20 @@ func TestRunBad(t *testing.T) { }) } +func TestHelpBad(t *testing.T) { + res := icmd.RunCmd(icmd.Command("docker", "help", "badmeta")) + res.Assert(t, icmd.Expected{ + ExitCode: 1, + Err: "unknown help topic: badmeta", + }) + + res = icmd.RunCmd(icmd.Command("docker", "badmeta", "--help")) + res.Assert(t, icmd.Expected{ + ExitCode: 0, + Out: "Usage: docker [OPTIONS] COMMAND", + }) +} + func TestRunGood(t *testing.T) { res := icmd.RunCmd(icmd.Command("docker", "helloworld")) res.Assert(t, icmd.Expected{ @@ -29,3 +57,16 @@ func TestRunGood(t *testing.T) { Out: "Hello World!", }) } + +func TestHelpGood(t *testing.T) { + res := icmd.RunCmd(icmd.Command("docker", "help", "helloworld")) + res.Assert(t, icmd.Expected{ + ExitCode: 0, + Out: "Usage: docker helloworld", + }) + res = icmd.RunCmd(icmd.Command("docker", "helloworld", "--help")) + res.Assert(t, icmd.Expected{ + ExitCode: 0, + Out: "Usage: docker helloworld", + }) +}