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

View File

@ -4,8 +4,10 @@ import (
"testing" "testing"
pluginmanager "github.com/docker/cli/cli-plugins/manager" pluginmanager "github.com/docker/cli/cli-plugins/manager"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gotest.tools/assert" "gotest.tools/assert"
is "gotest.tools/assert/cmp"
) )
func TestVisitAll(t *testing.T) { 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 // - Each of the main headings
// - Some builtin commands under the main headings // - Some builtin commands under the main headings
// - The `helloworld` plugin in the appropriate place // - 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. // Regexps are needed because the width depends on `unix.TIOCGWINSZ` or similar.
for _, expected := range []*regexp.Regexp{ for _, expected := range []*regexp.Regexp{
@ -41,6 +42,8 @@ func TestGlobalHelp(t *testing.T) {
regexp.MustCompile(`^ create\s+Create a new container$`), regexp.MustCompile(`^ create\s+Create a new container$`),
regexp.MustCompile(`^ helloworld\s+\(Docker Inc\.\)\s+A basic Hello World plugin for tests$`), regexp.MustCompile(`^ helloworld\s+\(Docker Inc\.\)\s+A basic Hello World plugin for tests$`),
regexp.MustCompile(`^ ps\s+List containers$`), 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 var found bool
for scanner.Scan() { for scanner.Scan() {