Skip to content

Commit

Permalink
Merge pull request #3429 from ndeloof/cobra_completion_v2
Browse files Browse the repository at this point in the history
Cobra completion v2 with CLI plugin support
  • Loading branch information
thaJeztah authored May 12, 2022
2 parents 2b4ffb3 + cbec75e commit a09e61a
Show file tree
Hide file tree
Showing 93 changed files with 538 additions and 61 deletions.
48 changes: 41 additions & 7 deletions cli-plugins/manager/cobra.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package manager

import (
"fmt"
"os"

"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -31,12 +34,13 @@ const (
// AddPluginCommandStubs adds a stub cobra.Commands for each valid and invalid
// plugin. The command stubs will have several annotations added, see
// `CommandAnnotationPlugin*`.
func AddPluginCommandStubs(dockerCli command.Cli, cmd *cobra.Command) error {
plugins, err := ListPlugins(dockerCli, cmd)
func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) error {
plugins, err := ListPlugins(dockerCli, rootCmd)
if err != nil {
return err
}
for _, p := range plugins {
p := p
vendor := p.Vendor
if vendor == "" {
vendor = "unknown"
Expand All @@ -49,11 +53,41 @@ func AddPluginCommandStubs(dockerCli command.Cli, cmd *cobra.Command) error {
if p.Err != nil {
annotations[CommandAnnotationPluginInvalid] = p.Err.Error()
}
cmd.AddCommand(&cobra.Command{
Use: p.Name,
Short: p.ShortDescription,
Run: func(_ *cobra.Command, _ []string) {},
Annotations: annotations,
rootCmd.AddCommand(&cobra.Command{
Use: p.Name,
Short: p.ShortDescription,
Run: func(_ *cobra.Command, _ []string) {},
Annotations: annotations,
DisableFlagParsing: true,
RunE: func(cmd *cobra.Command, args []string) error {
flags := rootCmd.PersistentFlags()
flags.SetOutput(nil)
err := flags.Parse(args)
if err != nil {
return err
}
if flags.Changed("help") {
cmd.HelpFunc()(rootCmd, args)
return nil
}
return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'", cmd.Name())
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Delegate completion to plugin
cargs := []string{p.Path, cobra.ShellCompRequestCmd, p.Name}
cargs = append(cargs, args...)
cargs = append(cargs, toComplete)
os.Args = cargs
runCommand, err := PluginRunCommand(dockerCli, p.Name, cmd)
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
err = runCommand.Run()
if err == nil {
os.Exit(0) // plugin already rendered complete data
}
return nil, cobra.ShellCompDirectiveError
},
})
}
return nil
Expand Down
5 changes: 5 additions & 0 deletions cli-plugins/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta
},
TraverseChildren: true,
DisableFlagsInUseLine: true,
CompletionOptions: cobra.CompletionOptions{
DisableDefaultCmd: false,
HiddenDefaultCmd: true,
DisableDescriptions: true,
},
}
opts, flags := cli.SetupPluginRootCommand(cmd)

Expand Down
4 changes: 3 additions & 1 deletion cli/command/builder/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
units "github.com/docker/go-units"
Expand Down Expand Up @@ -39,7 +40,8 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
return nil
},
Annotations: map[string]string{"version": "1.39"},
Annotations: map[string]string{"version": "1.39"},
ValidArgsFunction: completion.NoComplete,
}

flags := cmd.Flags()
Expand Down
2 changes: 2 additions & 0 deletions cli/command/checkpoint/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
Expand All @@ -25,6 +26,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return runList(dockerCli, args[0], opts)
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
}

flags := cmd.Flags()
Expand Down
99 changes: 99 additions & 0 deletions cli/command/completion/functions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package completion

import (
"os"

"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/spf13/cobra"
)

// ValidArgsFn a function to be used by cobra command as `ValidArgsFunction` to offer command line completion
type ValidArgsFn func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective)

// ImageNames offers completion for images present within the local store
func ImageNames(dockerCli command.Cli) ValidArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCli.Client().ImageList(cmd.Context(), types.ImageListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, image := range list {
names = append(names, image.RepoTags...)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
}

// ContainerNames offers completion for container names and IDs
// By default, only names are returned.
// Set DOCKER_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs.
func ContainerNames(dockerCli command.Cli, all bool, filters ...func(types.Container) bool) ValidArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCli.Client().ContainerList(cmd.Context(), types.ContainerListOptions{
All: all,
})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}

showContainerIDs := os.Getenv("DOCKER_COMPLETION_SHOW_CONTAINER_IDS") == "yes"

var names []string
for _, container := range list {
skip := false
for _, fn := range filters {
if !fn(container) {
skip = true
break
}
}
if skip {
continue
}
if showContainerIDs {
names = append(names, container.ID)
}
names = append(names, formatter.StripNamePrefix(container.Names)...)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
}

// VolumeNames offers completion for volumes
func VolumeNames(dockerCli command.Cli) ValidArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCli.Client().VolumeList(cmd.Context(), filters.Args{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, volume := range list.Volumes {
names = append(names, volume.Name)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
}

// NetworkNames offers completion for networks
func NetworkNames(dockerCli command.Cli) ValidArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCli.Client().NetworkList(cmd.Context(), types.NetworkListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, network := range list {
names = append(names, network.Name)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
}

// NoComplete is used for commands where there's no relevant completion
func NoComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
20 changes: 18 additions & 2 deletions cli/command/config/cmd.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package config

import (
"github.com/spf13/cobra"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
)

// NewConfigCommand returns a cobra command for `config` subcommands
Expand All @@ -27,3 +28,18 @@ func NewConfigCommand(dockerCli command.Cli) *cobra.Command {
)
return cmd
}

// completeNames offers completion for swarm configs
func completeNames(dockerCli command.Cli) completion.ValidArgsFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCli.Client().ConfigList(cmd.Context(), types.ConfigListOptions{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, config := range list {
names = append(names, config.ID)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
}
3 changes: 3 additions & 0 deletions cli/command/config/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ func newConfigInspectCommand(dockerCli command.Cli) *cobra.Command {
opts.Names = args
return RunConfigInspect(dockerCli, opts)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completeNames(dockerCli)(cmd, args, toComplete)
},
}

cmd.Flags().StringVarP(&opts.Format, "format", "f", "", flagsHelper.InspectFormatHelp)
Expand Down
2 changes: 2 additions & 0 deletions cli/command/config/ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
Expand All @@ -32,6 +33,7 @@ func newConfigListCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return RunConfigList(dockerCli, listOpts)
},
ValidArgsFunction: completion.NoComplete,
}

flags := cmd.Flags()
Expand Down
3 changes: 3 additions & 0 deletions cli/command/config/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ func newConfigRemoveCommand(dockerCli command.Cli) *cobra.Command {
}
return RunConfigRemove(dockerCli, opts)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completeNames(dockerCli)(cmd, args, toComplete)
},
}
}

Expand Down
2 changes: 2 additions & 0 deletions cli/command/container/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
Expand Down Expand Up @@ -54,6 +55,7 @@ func NewAttachCommand(dockerCli command.Cli) *cobra.Command {
opts.container = args[0]
return runAttach(dockerCli, &opts)
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
}

flags := cmd.Flags()
Expand Down
2 changes: 2 additions & 0 deletions cli/command/container/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -36,6 +37,7 @@ func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
}
return runCommit(dockerCli, &options)
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
}

flags := cmd.Flags()
Expand Down
2 changes: 2 additions & 0 deletions cli/command/container/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/containerd/containerd/platforms"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/opts"
"github.com/docker/distribution/reference"
Expand Down Expand Up @@ -56,6 +57,7 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
}
return runCreate(dockerCli, cmd.Flags(), &opts, copts)
},
ValidArgsFunction: completion.ImageNames(dockerCli),
}

flags := cmd.Flags()
Expand Down
2 changes: 2 additions & 0 deletions cli/command/container/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand All @@ -26,6 +27,7 @@ func NewDiffCommand(dockerCli command.Cli) *cobra.Command {
opts.container = args[0]
return runDiff(dockerCli, &opts)
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
}
}

Expand Down
16 changes: 16 additions & 0 deletions cli/command/container/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"context"
"fmt"
"io"
"os"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
Expand Down Expand Up @@ -52,6 +54,7 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
options.Command = args[1:]
return RunExec(dockerCli, options)
},
ValidArgsFunction: completion.ContainerNames(dockerCli, false),
Annotations: map[string]string{
"category-top": "2",
},
Expand All @@ -73,6 +76,19 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
flags.StringVarP(&options.Workdir, "workdir", "w", "", "Working directory inside the container")
flags.SetAnnotation("workdir", "version", []string{"1.35"})

cmd.RegisterFlagCompletionFunc(
"env",
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return os.Environ(), cobra.ShellCompDirectiveNoFileComp
},
)
cmd.RegisterFlagCompletionFunc(
"env-file",
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveDefault // _filedir
},
)

return cmd
}

Expand Down
Loading

0 comments on commit a09e61a

Please sign in to comment.