Output broken CLI plugins in `help` output.

Signed-off-by: Ian Campbell <ijc@docker.com>
This commit is contained in:
Ian Campbell 2018-12-19 11:29:01 +00:00
parent e5e578abc7
commit 0ab8ec0e4c
3 changed files with 68 additions and 0 deletions

View File

@ -24,11 +24,14 @@ func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *p
cobra.AddTemplateFunc("hasSubCommands", hasSubCommands)
cobra.AddTemplateFunc("hasManagementSubCommands", hasManagementSubCommands)
cobra.AddTemplateFunc("hasInvalidPlugins", hasInvalidPlugins)
cobra.AddTemplateFunc("operationSubCommands", operationSubCommands)
cobra.AddTemplateFunc("managementSubCommands", managementSubCommands)
cobra.AddTemplateFunc("invalidPlugins", invalidPlugins)
cobra.AddTemplateFunc("wrappedFlagUsages", wrappedFlagUsages)
cobra.AddTemplateFunc("commandVendor", commandVendor)
cobra.AddTemplateFunc("isFirstLevelCommand", isFirstLevelCommand) // is it an immediate sub-command of the root
cobra.AddTemplateFunc("invalidPluginReason", invalidPluginReason)
rootCmd.SetUsageTemplate(usageTemplate)
rootCmd.SetHelpTemplate(helpTemplate)
@ -115,6 +118,10 @@ var helpCommand = &cobra.Command{
},
}
func isPlugin(cmd *cobra.Command) bool {
return cmd.Annotations[pluginmanager.CommandAnnotationPlugin] == "true"
}
func hasSubCommands(cmd *cobra.Command) bool {
return len(operationSubCommands(cmd)) > 0
}
@ -123,9 +130,16 @@ func hasManagementSubCommands(cmd *cobra.Command) bool {
return len(managementSubCommands(cmd)) > 0
}
func hasInvalidPlugins(cmd *cobra.Command) bool {
return len(invalidPlugins(cmd)) > 0
}
func operationSubCommands(cmd *cobra.Command) []*cobra.Command {
cmds := []*cobra.Command{}
for _, sub := range cmd.Commands() {
if isPlugin(sub) && invalidPluginReason(sub) != "" {
continue
}
if sub.IsAvailableCommand() && !sub.HasSubCommands() {
cmds = append(cmds, sub)
}
@ -159,6 +173,9 @@ func commandVendor(cmd *cobra.Command) string {
func managementSubCommands(cmd *cobra.Command) []*cobra.Command {
cmds := []*cobra.Command{}
for _, sub := range cmd.Commands() {
if isPlugin(sub) && invalidPluginReason(sub) != "" {
continue
}
if sub.IsAvailableCommand() && sub.HasSubCommands() {
cmds = append(cmds, sub)
}
@ -166,6 +183,23 @@ func managementSubCommands(cmd *cobra.Command) []*cobra.Command {
return cmds
}
func invalidPlugins(cmd *cobra.Command) []*cobra.Command {
cmds := []*cobra.Command{}
for _, sub := range cmd.Commands() {
if !isPlugin(sub) {
continue
}
if invalidPluginReason(sub) != "" {
cmds = append(cmds, sub)
}
}
return cmds
}
func invalidPluginReason(cmd *cobra.Command) string {
return cmd.Annotations[pluginmanager.CommandAnnotationPluginInvalid]
}
var usageTemplate = `Usage:
{{- if not .HasSubCommands}} {{.UseLine}}{{end}}
@ -209,6 +243,16 @@ Commands:
{{- end}}
{{- end}}
{{- if hasInvalidPlugins . }}
Invalid Plugins:
{{- range invalidPlugins . }}
{{rpad .Name .NamePadding }} {{invalidPluginReason .}}
{{- end}}
{{- end}}
{{- if .HasSubCommands }}
Run '{{.CommandPath}} COMMAND --help' for more information on a command.

View File

@ -4,8 +4,10 @@ import (
"testing"
pluginmanager "github.com/docker/cli/cli-plugins/manager"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/spf13/cobra"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestVisitAll(t *testing.T) {
@ -55,3 +57,22 @@ func TestCommandVendor(t *testing.T) {
})
}
}
func TestInvalidPlugin(t *testing.T) {
root := &cobra.Command{Use: "root"}
sub1 := &cobra.Command{Use: "sub1"}
sub1sub1 := &cobra.Command{Use: "sub1sub1"}
sub1sub2 := &cobra.Command{Use: "sub1sub2"}
sub2 := &cobra.Command{Use: "sub2"}
assert.Assert(t, is.Len(invalidPlugins(root), 0))
sub1.Annotations = map[string]string{
pluginmanager.CommandAnnotationPlugin: "true",
pluginmanager.CommandAnnotationPluginInvalid: "foo",
}
root.AddCommand(sub1, sub2)
sub1.AddCommand(sub1sub1, sub1sub2)
assert.DeepEqual(t, invalidPlugins(root), []*cobra.Command{sub1}, cmpopts.IgnoreUnexported(cobra.Command{}))
}

View File

@ -31,6 +31,7 @@ func TestGlobalHelp(t *testing.T) {
// - Each of the main headings
// - Some builtin commands under the main headings
// - The `helloworld` plugin in the appropriate place
// - The `badmeta` plugin under the "Invalid Plugins" heading.
//
// Regexps are needed because the width depends on `unix.TIOCGWINSZ` or similar.
for _, expected := range []*regexp.Regexp{
@ -41,6 +42,8 @@ func TestGlobalHelp(t *testing.T) {
regexp.MustCompile(`^ create\s+Create a new container$`),
regexp.MustCompile(`^ helloworld\s+\(Docker Inc\.\)\s+A basic Hello World plugin for tests$`),
regexp.MustCompile(`^ ps\s+List containers$`),
regexp.MustCompile(`^Invalid Plugins:$`),
regexp.MustCompile(`^ badmeta\s+invalid metadata: invalid character 'i' looking for beginning of object key string$`),
} {
var found bool
for scanner.Scan() {