Integrate CLI plugins with `docker «plugin» --help`.

To achieve this we hook in at the beginning of our custom `HelpFunc` and detect
the plugin case by adding stub commands.

Signed-off-by: Ian Campbell <ijc@docker.com>
This commit is contained in:
Ian Campbell 2018-12-17 16:23:55 +00:00
parent 20a284721c
commit 53f018120a
2 changed files with 86 additions and 0 deletions

View File

@ -123,6 +123,21 @@ func setupHelpCommand(dockerCli *command.DockerCli, rootCmd, helpCmd *cobra.Comm
} }
} }
func tryRunPluginHelp(dockerCli command.Cli, ccmd *cobra.Command, cargs []string) error {
root := ccmd.Root()
pluginmanager.AddPluginCommandStubs(dockerCli, root, false)
cmd, _, err := root.Traverse(cargs)
if err != nil {
return err
}
helpcmd, err := pluginmanager.PluginRunCommand(dockerCli, cmd.Name(), root)
if err != nil {
return err
}
return helpcmd.Run()
}
func setHelpFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) { func setHelpFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.FlagSet, opts *cliflags.ClientOptions) {
defaultHelpFunc := cmd.HelpFunc() defaultHelpFunc := cmd.HelpFunc()
cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) { cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) {
@ -130,6 +145,17 @@ func setHelpFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.
ccmd.Println(err) ccmd.Println(err)
return return
} }
if len(args) >= 1 {
err := tryRunPluginHelp(dockerCli, ccmd, args)
if err == nil { // Successfully ran the plugin
return
}
if !pluginmanager.IsNotFound(err) {
ccmd.Println(err)
return
}
}
if err := isSupported(ccmd, dockerCli); err != nil { if err := isSupported(ccmd, dockerCli); err != nil {
ccmd.Println(err) ccmd.Println(err)
return return

View File

@ -35,6 +35,22 @@ func TestHelpNonexisting(t *testing.T) {
golden.Assert(t, res.Stderr(), "docker-help-nonexistent-err.golden") golden.Assert(t, res.Stderr(), "docker-help-nonexistent-err.golden")
} }
// TestNonexistingHelp ensures correct behaviour when invoking a
// nonexistent plugin with `--help`.
func TestNonexistingHelp(t *testing.T) {
run, cleanup := prepare(t)
defer cleanup()
res := icmd.RunCmd(run("nonexistent", "--help"))
res.Assert(t, icmd.Expected{
ExitCode: 0,
// This should actually be the whole docker help
// output, so spot check instead having of a golden
// with everything in, which will change too frequently.
Out: "Usage: docker [OPTIONS] COMMAND\n\nA self-sufficient runtime for containers",
})
}
// TestRunBad ensures correct behaviour when running an existent but invalid plugin // TestRunBad ensures correct behaviour when running an existent but invalid plugin
func TestRunBad(t *testing.T) { func TestRunBad(t *testing.T) {
run, cleanup := prepare(t) run, cleanup := prepare(t)
@ -61,6 +77,22 @@ func TestHelpBad(t *testing.T) {
golden.Assert(t, res.Stderr(), "docker-help-badmeta-err.golden") golden.Assert(t, res.Stderr(), "docker-help-badmeta-err.golden")
} }
// TestBadHelp ensures correct behaviour when invoking an
// existent but invalid plugin with `--help`.
func TestBadHelp(t *testing.T) {
run, cleanup := prepare(t)
defer cleanup()
res := icmd.RunCmd(run("badmeta", "--help"))
res.Assert(t, icmd.Expected{
ExitCode: 0,
// This should be literally the whole docker help
// output, so spot check instead of a golden with
// everything in which will change all the time.
Out: "Usage: docker [OPTIONS] COMMAND\n\nA self-sufficient runtime for containers",
})
}
// TestRunGood ensures correct behaviour when running a valid plugin // TestRunGood ensures correct behaviour when running a valid plugin
func TestRunGood(t *testing.T) { func TestRunGood(t *testing.T) {
run, cleanup := prepare(t) run, cleanup := prepare(t)
@ -86,6 +118,20 @@ func TestHelpGood(t *testing.T) {
assert.Assert(t, is.Equal(res.Stderr(), "")) assert.Assert(t, is.Equal(res.Stderr(), ""))
} }
// TestGoodHelp ensures correct behaviour when calling a valid plugin
// with `--help`. A global argument is used to ensure it does not
// interfere.
func TestGoodHelp(t *testing.T) {
run, cleanup := prepare(t)
defer cleanup()
res := icmd.RunCmd(run("-D", "helloworld", "--help"))
res.Assert(t, icmd.Success)
// This is the same golden file as `TestHelpGood`, above.
golden.Assert(t, res.Stdout(), "docker-help-helloworld.golden")
assert.Assert(t, is.Equal(res.Stderr(), ""))
}
// TestRunGoodSubcommand ensures correct behaviour when running a valid plugin with a subcommand // TestRunGoodSubcommand ensures correct behaviour when running a valid plugin with a subcommand
func TestRunGoodSubcommand(t *testing.T) { func TestRunGoodSubcommand(t *testing.T) {
run, cleanup := prepare(t) run, cleanup := prepare(t)
@ -110,3 +156,17 @@ func TestHelpGoodSubcommand(t *testing.T) {
golden.Assert(t, res.Stdout(), "docker-help-helloworld-goodbye.golden") golden.Assert(t, res.Stdout(), "docker-help-helloworld-goodbye.golden")
assert.Assert(t, is.Equal(res.Stderr(), "")) assert.Assert(t, is.Equal(res.Stderr(), ""))
} }
// TestGoodSubcommandHelp ensures correct behaviour when calling a valid plugin
// with a subcommand and `--help`. A global argument is used to ensure it does not
// interfere.
func TestGoodSubcommandHelp(t *testing.T) {
run, cleanup := prepare(t)
defer cleanup()
res := icmd.RunCmd(run("-D", "helloworld", "goodbye", "--help"))
res.Assert(t, icmd.Success)
// This is the same golden file as `TestHelpGoodSubcommand`, above.
golden.Assert(t, res.Stdout(), "docker-help-helloworld-goodbye.golden")
assert.Assert(t, is.Equal(res.Stderr(), ""))
}