From cbec75e2f3f5afd3334db693829a6b150612076c Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 30 Mar 2022 15:27:25 +0200 Subject: [PATCH] Adopt Cobra completion v2 to support completion by CLI plugins Signed-off-by: Nicolas De Loof --- cli-plugins/manager/cobra.go | 48 ++++++++++++-- cli-plugins/plugin/plugin.go | 5 ++ cli/command/builder/prune.go | 4 +- cli/command/checkpoint/list.go | 2 + cli/command/completion/functions.go | 99 +++++++++++++++++++++++++++++ cli/command/config/cmd.go | 20 +++++- cli/command/config/inspect.go | 3 + cli/command/config/ls.go | 2 + cli/command/config/remove.go | 3 + cli/command/container/attach.go | 2 + cli/command/container/commit.go | 2 + cli/command/container/create.go | 2 + cli/command/container/diff.go | 2 + cli/command/container/exec.go | 16 +++++ cli/command/container/export.go | 2 + cli/command/container/inspect.go | 2 + cli/command/container/kill.go | 2 + cli/command/container/list.go | 2 + cli/command/container/logs.go | 2 + cli/command/container/pause.go | 5 ++ cli/command/container/port.go | 2 + cli/command/container/prune.go | 4 +- cli/command/container/rename.go | 2 + cli/command/container/restart.go | 2 + cli/command/container/rm.go | 2 + cli/command/container/run.go | 20 ++++++ cli/command/container/start.go | 4 ++ cli/command/container/stats.go | 2 + cli/command/container/stop.go | 2 + cli/command/container/top.go | 2 + cli/command/container/unpause.go | 5 ++ cli/command/container/update.go | 2 + cli/command/container/wait.go | 2 + cli/command/context/list.go | 2 + cli/command/context/show.go | 2 + cli/command/formatter/container.go | 11 +++- cli/command/formatter/custom.go | 9 --- cli/command/image/build.go | 3 + cli/command/image/cmd.go | 3 +- cli/command/image/load.go | 2 + cli/command/image/prune.go | 4 +- cli/command/image/push.go | 2 + cli/command/network/cmd.go | 3 +- cli/command/network/connect.go | 8 +++ cli/command/network/disconnect.go | 26 ++++++++ cli/command/network/inspect.go | 2 + cli/command/network/list.go | 2 + cli/command/network/remove.go | 2 + cli/command/node/list.go | 2 + cli/command/node/ps.go | 2 + cli/command/plugin/list.go | 2 + cli/command/secret/cmd.go | 20 +++++- cli/command/secret/inspect.go | 3 + cli/command/secret/ls.go | 3 + cli/command/secret/remove.go | 3 + cli/command/service/cmd.go | 20 +++++- cli/command/service/inspect.go | 3 + cli/command/service/list.go | 2 + cli/command/service/logs.go | 3 + cli/command/service/ps.go | 3 + cli/command/service/remove.go | 3 + cli/command/service/rollback.go | 3 + cli/command/service/scale.go | 3 + cli/command/service/update.go | 3 + cli/command/stack/cmd.go | 17 +++++ cli/command/stack/config.go | 2 + cli/command/stack/deploy.go | 3 + cli/command/stack/list.go | 2 + cli/command/stack/ps.go | 3 + cli/command/stack/remove.go | 3 + cli/command/stack/services.go | 3 + cli/command/swarm/ca.go | 2 + cli/command/swarm/init.go | 2 + cli/command/swarm/leave.go | 2 + cli/command/swarm/unlock.go | 2 + cli/command/swarm/unlock_key.go | 2 + cli/command/swarm/update.go | 2 + cli/command/system/df.go | 4 +- cli/command/system/dial_stdio.go | 2 + cli/command/system/events.go | 2 + cli/command/system/info.go | 2 + cli/command/system/prune.go | 4 +- cli/command/system/version.go | 2 + cli/command/volume/inspect.go | 2 + cli/command/volume/list.go | 2 + cli/command/volume/prune.go | 4 +- cli/command/volume/remove.go | 2 + cli/context/store/store.go | 13 ++++ cmd/docker/completions.go | 27 ++++++++ cmd/docker/docker.go | 42 ++++++------ cmd/docker/docker_test.go | 1 + e2e/cli-plugins/run_test.go | 8 +-- e2e/context/context_test.go | 2 +- 93 files changed, 538 insertions(+), 61 deletions(-) create mode 100644 cli/command/completion/functions.go create mode 100644 cmd/docker/completions.go diff --git a/cli-plugins/manager/cobra.go b/cli-plugins/manager/cobra.go index 0fcd73e7b6..71b6fe716d 100644 --- a/cli-plugins/manager/cobra.go +++ b/cli-plugins/manager/cobra.go @@ -1,6 +1,9 @@ package manager import ( + "fmt" + "os" + "github.com/docker/cli/cli/command" "github.com/spf13/cobra" ) @@ -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" @@ -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 diff --git a/cli-plugins/plugin/plugin.go b/cli-plugins/plugin/plugin.go index 970e487e3c..3d98662220 100644 --- a/cli-plugins/plugin/plugin.go +++ b/cli-plugins/plugin/plugin.go @@ -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) diff --git a/cli/command/builder/prune.go b/cli/command/builder/prune.go index 573a4a93d2..e814368cb8 100644 --- a/cli/command/builder/prune.go +++ b/cli/command/builder/prune.go @@ -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" @@ -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() diff --git a/cli/command/checkpoint/list.go b/cli/command/checkpoint/list.go index fda23e2980..1c7b9fbac9 100644 --- a/cli/command/checkpoint/list.go +++ b/cli/command/checkpoint/list.go @@ -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" @@ -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() diff --git a/cli/command/completion/functions.go b/cli/command/completion/functions.go new file mode 100644 index 0000000000..484ab44812 --- /dev/null +++ b/cli/command/completion/functions.go @@ -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 +} diff --git a/cli/command/config/cmd.go b/cli/command/config/cmd.go index cc19bb2325..86ad1cc09c 100644 --- a/cli/command/config/cmd.go +++ b/cli/command/config/cmd.go @@ -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 @@ -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 + } +} diff --git a/cli/command/config/inspect.go b/cli/command/config/inspect.go index a5b57c776f..1f0abd91c3 100644 --- a/cli/command/config/inspect.go +++ b/cli/command/config/inspect.go @@ -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) diff --git a/cli/command/config/ls.go b/cli/command/config/ls.go index af463adbc6..8fbabd2301 100644 --- a/cli/command/config/ls.go +++ b/cli/command/config/ls.go @@ -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" @@ -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() diff --git a/cli/command/config/remove.go b/cli/command/config/remove.go index 7c30bd4636..0818d601c6 100644 --- a/cli/command/config/remove.go +++ b/cli/command/config/remove.go @@ -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) + }, } } diff --git a/cli/command/container/attach.go b/cli/command/container/attach.go index 287159b97e..350df917d5 100644 --- a/cli/command/container/attach.go +++ b/cli/command/container/attach.go @@ -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" @@ -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() diff --git a/cli/command/container/commit.go b/cli/command/container/commit.go index 0a30f55d45..bf177b2468 100644 --- a/cli/command/container/commit.go +++ b/cli/command/container/commit.go @@ -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" @@ -36,6 +37,7 @@ func NewCommitCommand(dockerCli command.Cli) *cobra.Command { } return runCommit(dockerCli, &options) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, false), } flags := cmd.Flags() diff --git a/cli/command/container/create.go b/cli/command/container/create.go index b8bf86e2ac..eaa4720c42 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -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" @@ -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() diff --git a/cli/command/container/diff.go b/cli/command/container/diff.go index 1cfb63c654..ee57a82033 100644 --- a/cli/command/container/diff.go +++ b/cli/command/container/diff.go @@ -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" @@ -26,6 +27,7 @@ func NewDiffCommand(dockerCli command.Cli) *cobra.Command { opts.container = args[0] return runDiff(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, false), } } diff --git a/cli/command/container/exec.go b/cli/command/container/exec.go index 9dc00724f2..414e42d9a7 100644 --- a/cli/command/container/exec.go +++ b/cli/command/container/exec.go @@ -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" @@ -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", }, @@ -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 } diff --git a/cli/command/container/export.go b/cli/command/container/export.go index ee77cdb55d..6ad2e2634c 100644 --- a/cli/command/container/export.go +++ b/cli/command/container/export.go @@ -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/pkg/errors" "github.com/spf13/cobra" ) @@ -27,6 +28,7 @@ func NewExportCommand(dockerCli command.Cli) *cobra.Command { opts.container = args[0] return runExport(dockerCli, opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, true), } flags := cmd.Flags() diff --git a/cli/command/container/inspect.go b/cli/command/container/inspect.go index b5d4060789..b9e320b827 100644 --- a/cli/command/container/inspect.go +++ b/cli/command/container/inspect.go @@ -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/inspect" flagsHelper "github.com/docker/cli/cli/flags" "github.com/spf13/cobra" @@ -28,6 +29,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command { opts.refs = args return runInspect(dockerCli, opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, true), } flags := cmd.Flags() diff --git a/cli/command/container/kill.go b/cli/command/container/kill.go index feedbc0111..8c74416d1b 100644 --- a/cli/command/container/kill.go +++ b/cli/command/container/kill.go @@ -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/pkg/errors" "github.com/spf13/cobra" ) @@ -29,6 +30,7 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command { opts.containers = args return runKill(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, false), } flags := cmd.Flags() diff --git a/cli/command/container/list.go b/cli/command/container/list.go index b24ce8b09c..f76a9fa947 100644 --- a/cli/command/container/list.go +++ b/cli/command/container/list.go @@ -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" @@ -40,6 +41,7 @@ func NewPsCommand(dockerCli command.Cli) *cobra.Command { Annotations: map[string]string{ "category-top": "3", }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/container/logs.go b/cli/command/container/logs.go index a67e2ee91d..d1263b5d3e 100644 --- a/cli/command/container/logs.go +++ b/cli/command/container/logs.go @@ -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/docker/api/types" "github.com/docker/docker/pkg/stdcopy" "github.com/spf13/cobra" @@ -34,6 +35,7 @@ func NewLogsCommand(dockerCli command.Cli) *cobra.Command { opts.container = args[0] return runLogs(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, true), } flags := cmd.Flags() diff --git a/cli/command/container/pause.go b/cli/command/container/pause.go index 1118b7f0d4..c048bafb0b 100644 --- a/cli/command/container/pause.go +++ b/cli/command/container/pause.go @@ -7,6 +7,8 @@ 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/pkg/errors" "github.com/spf13/cobra" ) @@ -27,6 +29,9 @@ func NewPauseCommand(dockerCli command.Cli) *cobra.Command { opts.containers = args return runPause(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(container types.Container) bool { + return container.State != "paused" + }), } } diff --git a/cli/command/container/port.go b/cli/command/container/port.go index dc769b40cf..71d527e8fb 100644 --- a/cli/command/container/port.go +++ b/cli/command/container/port.go @@ -8,6 +8,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command/completion" "github.com/docker/go-connections/nat" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -34,6 +35,7 @@ func NewPortCommand(dockerCli command.Cli) *cobra.Command { } return runPort(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, false), } return cmd } diff --git a/cli/command/container/prune.go b/cli/command/container/prune.go index 1c820feeb9..69b30269fc 100644 --- a/cli/command/container/prune.go +++ b/cli/command/container/prune.go @@ -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" units "github.com/docker/go-units" "github.com/spf13/cobra" @@ -35,7 +36,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.25"}, + Annotations: map[string]string{"version": "1.25"}, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/container/rename.go b/cli/command/container/rename.go index bc58ea2029..c5a74fc8e7 100644 --- a/cli/command/container/rename.go +++ b/cli/command/container/rename.go @@ -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/pkg/errors" "github.com/spf13/cobra" ) @@ -29,6 +30,7 @@ func NewRenameCommand(dockerCli command.Cli) *cobra.Command { opts.newName = args[1] return runRename(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, true), } return cmd } diff --git a/cli/command/container/restart.go b/cli/command/container/restart.go index b13bfe52da..7ac5751802 100644 --- a/cli/command/container/restart.go +++ b/cli/command/container/restart.go @@ -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/container" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -32,6 +33,7 @@ func NewRestartCommand(dockerCli command.Cli) *cobra.Command { opts.nSecondsChanged = cmd.Flags().Changed("time") return runRestart(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, true), } flags := cmd.Flags() diff --git a/cli/command/container/rm.go b/cli/command/container/rm.go index 2a2528c6f4..3b0c595f9c 100644 --- a/cli/command/container/rm.go +++ b/cli/command/container/rm.go @@ -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/errdefs" "github.com/pkg/errors" @@ -33,6 +34,7 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command { opts.containers = args return runRm(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, true), } flags := cmd.Flags() diff --git a/cli/command/container/run.go b/cli/command/container/run.go index 698e2a8c38..92748dca2d 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -4,11 +4,13 @@ import ( "context" "fmt" "io" + "os" "strings" "syscall" "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/docker/docker/api/types/container" @@ -43,6 +45,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { } return runRun(dockerCli, cmd.Flags(), &opts, copts) }, + ValidArgsFunction: completion.ImageNames(dockerCli), Annotations: map[string]string{ "category-top": "1", }, @@ -67,6 +70,23 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { command.AddPlatformFlag(flags, &opts.platform) command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled()) copts = addFlags(flags) + + 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 + }, + ) + cmd.RegisterFlagCompletionFunc( + "network", + completion.NetworkNames(dockerCli), + ) return cmd } diff --git a/cli/command/container/start.go b/cli/command/container/start.go index b86ce53ad9..b85d6e458d 100644 --- a/cli/command/container/start.go +++ b/cli/command/container/start.go @@ -8,6 +8,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/moby/sys/signal" "github.com/moby/term" @@ -43,6 +44,9 @@ func NewStartCommand(dockerCli command.Cli) *cobra.Command { opts.Containers = args return RunStart(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, true, func(container types.Container) bool { + return container.State == "exited" || container.State == "created" + }), } flags := cmd.Flags() diff --git a/cli/command/container/stats.go b/cli/command/container/stats.go index 50b1093780..38d89450c4 100644 --- a/cli/command/container/stats.go +++ b/cli/command/container/stats.go @@ -10,6 +10,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/docker/api/types" @@ -39,6 +40,7 @@ func NewStatsCommand(dockerCli command.Cli) *cobra.Command { opts.containers = args return runStats(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, false), } flags := cmd.Flags() diff --git a/cli/command/container/stop.go b/cli/command/container/stop.go index e7a7df7984..bcceb81351 100644 --- a/cli/command/container/stop.go +++ b/cli/command/container/stop.go @@ -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/container" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -32,6 +33,7 @@ func NewStopCommand(dockerCli command.Cli) *cobra.Command { opts.timeChanged = cmd.Flags().Changed("time") return runStop(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, false), } flags := cmd.Flags() diff --git a/cli/command/container/top.go b/cli/command/container/top.go index a7d9142ddd..98300e530a 100644 --- a/cli/command/container/top.go +++ b/cli/command/container/top.go @@ -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/cli/command/formatter/tabwriter" "github.com/spf13/cobra" ) @@ -30,6 +31,7 @@ func NewTopCommand(dockerCli command.Cli) *cobra.Command { opts.args = args[1:] return runTop(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, false), } flags := cmd.Flags() diff --git a/cli/command/container/unpause.go b/cli/command/container/unpause.go index 7af4547fdb..d03cda1c13 100644 --- a/cli/command/container/unpause.go +++ b/cli/command/container/unpause.go @@ -7,6 +7,8 @@ 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/pkg/errors" "github.com/spf13/cobra" ) @@ -27,6 +29,9 @@ func NewUnpauseCommand(dockerCli command.Cli) *cobra.Command { opts.containers = args return runUnpause(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(container types.Container) bool { + return container.State == "paused" + }), } return cmd } diff --git a/cli/command/container/update.go b/cli/command/container/update.go index 4d1b525ca5..caa07b62d5 100644 --- a/cli/command/container/update.go +++ b/cli/command/container/update.go @@ -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" containertypes "github.com/docker/docker/api/types/container" "github.com/pkg/errors" @@ -48,6 +49,7 @@ func NewUpdateCommand(dockerCli command.Cli) *cobra.Command { options.nFlag = cmd.Flags().NFlag() return runUpdate(dockerCli, &options) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, true), } flags := cmd.Flags() diff --git a/cli/command/container/wait.go b/cli/command/container/wait.go index 8602e25398..ad54f22922 100644 --- a/cli/command/container/wait.go +++ b/cli/command/container/wait.go @@ -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/pkg/errors" "github.com/spf13/cobra" ) @@ -27,6 +28,7 @@ func NewWaitCommand(dockerCli command.Cli) *cobra.Command { opts.containers = args return runWait(dockerCli, &opts) }, + ValidArgsFunction: completion.ContainerNames(dockerCli, false), } return cmd diff --git a/cli/command/context/list.go b/cli/command/context/list.go index a1b2af53bc..9833581eed 100644 --- a/cli/command/context/list.go +++ b/cli/command/context/list.go @@ -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/cli/command/formatter" "github.com/docker/cli/cli/context/docker" flagsHelper "github.com/docker/cli/cli/flags" @@ -30,6 +31,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(dockerCli, opts) }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/context/show.go b/cli/command/context/show.go index 6d87154079..0bcdfabee1 100644 --- a/cli/command/context/show.go +++ b/cli/command/context/show.go @@ -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/spf13/cobra" ) @@ -17,6 +18,7 @@ func newShowCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runShow(dockerCli) }, + ValidArgsFunction: completion.NoComplete, } return cmd } diff --git a/cli/command/formatter/container.go b/cli/command/formatter/container.go index ade53b67f6..b22b36828d 100644 --- a/cli/command/formatter/container.go +++ b/cli/command/formatter/container.go @@ -125,7 +125,7 @@ func (c *ContainerContext) ID() string { // slash (/) prefix stripped. Additional names for the container (related to the // legacy `--link` feature) are omitted. func (c *ContainerContext) Names() string { - names := stripNamePrefix(c.c.Names) + names := StripNamePrefix(c.c.Names) if c.trunc { for _, name := range names { if len(strings.Split(name, "/")) == 1 { @@ -137,6 +137,15 @@ func (c *ContainerContext) Names() string { return strings.Join(names, ",") } +// StripNamePrefix removes prefix from string, typically container names as returned by `ContainersList` API +func StripNamePrefix(ss []string) []string { + sss := make([]string, len(ss)) + for i, s := range ss { + sss[i] = s[1:] + } + return sss +} + // Image returns the container's image reference. If the trunc option is set, // the image's registry digest can be included. func (c *ContainerContext) Image() string { diff --git a/cli/command/formatter/custom.go b/cli/command/formatter/custom.go index bb46b225fb..1d78ce62e2 100644 --- a/cli/command/formatter/custom.go +++ b/cli/command/formatter/custom.go @@ -45,12 +45,3 @@ type HeaderContext struct { func (c *HeaderContext) FullHeader() interface{} { return c.Header } - -func stripNamePrefix(ss []string) []string { - sss := make([]string, len(ss)) - for i, s := range ss { - sss[i] = s[1:] - } - - return sss -} diff --git a/cli/command/image/build.go b/cli/command/image/build.go index 7692109827..0c40b23b11 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -108,6 +108,9 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command { Annotations: map[string]string{ "category-top": "4", }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return nil, cobra.ShellCompDirectiveFilterDirs + }, } flags := cmd.Flags() diff --git a/cli/command/image/cmd.go b/cli/command/image/cmd.go index a12bf3395b..c035da17f0 100644 --- a/cli/command/image/cmd.go +++ b/cli/command/image/cmd.go @@ -1,10 +1,9 @@ package image import ( - "github.com/spf13/cobra" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/spf13/cobra" ) // NewImageCommand returns a cobra command for `image` subcommands diff --git a/cli/command/image/load.go b/cli/command/image/load.go index 6809c6203a..6c48f1743a 100644 --- a/cli/command/image/load.go +++ b/cli/command/image/load.go @@ -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/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/system" "github.com/pkg/errors" @@ -28,6 +29,7 @@ func NewLoadCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runLoad(dockerCli, opts) }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/image/prune.go b/cli/command/image/prune.go index bb16bbcfb5..9eb73d5b9c 100644 --- a/cli/command/image/prune.go +++ b/cli/command/image/prune.go @@ -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" units "github.com/docker/go-units" "github.com/spf13/cobra" @@ -37,7 +38,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.25"}, + Annotations: map[string]string{"version": "1.25"}, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/image/push.go b/cli/command/image/push.go index c422088ea2..47bcbd002e 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -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/cli/streams" "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" @@ -38,6 +39,7 @@ func NewPushCommand(dockerCli command.Cli) *cobra.Command { Annotations: map[string]string{ "category-top": "6", }, + ValidArgsFunction: completion.ImageNames(dockerCli), } flags := cmd.Flags() diff --git a/cli/command/network/cmd.go b/cli/command/network/cmd.go index 028e1377f0..3db3088ebe 100644 --- a/cli/command/network/cmd.go +++ b/cli/command/network/cmd.go @@ -1,10 +1,9 @@ package network import ( - "github.com/spf13/cobra" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/spf13/cobra" ) // NewNetworkCommand returns a cobra command for `network` subcommands diff --git a/cli/command/network/connect.go b/cli/command/network/connect.go index 04108e20fa..ea4a7aa023 100644 --- a/cli/command/network/connect.go +++ b/cli/command/network/connect.go @@ -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/network" "github.com/spf13/cobra" @@ -37,6 +38,13 @@ func newConnectCommand(dockerCli command.Cli) *cobra.Command { options.container = args[1] return runConnect(dockerCli, options) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return completion.NetworkNames(dockerCli)(cmd, args, toComplete) + } + network := args[0] + return completion.ContainerNames(dockerCli, true, not(isConnected(network)))(cmd, args, toComplete) + }, } flags := cmd.Flags() diff --git a/cli/command/network/disconnect.go b/cli/command/network/disconnect.go index 18bf4c7ba1..11f03b9254 100644 --- a/cli/command/network/disconnect.go +++ b/cli/command/network/disconnect.go @@ -5,6 +5,8 @@ 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/spf13/cobra" ) @@ -26,6 +28,13 @@ func newDisconnectCommand(dockerCli command.Cli) *cobra.Command { opts.container = args[1] return runDisconnect(dockerCli, opts) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return completion.NetworkNames(dockerCli)(cmd, args, toComplete) + } + network := args[0] + return completion.ContainerNames(dockerCli, true, isConnected(network))(cmd, args, toComplete) + }, } flags := cmd.Flags() @@ -39,3 +48,20 @@ func runDisconnect(dockerCli command.Cli, opts disconnectOptions) error { return client.NetworkDisconnect(context.Background(), opts.network, opts.container, opts.force) } + +func isConnected(network string) func(types.Container) bool { + return func(container types.Container) bool { + if container.NetworkSettings == nil { + return false + } + _, ok := container.NetworkSettings.Networks[network] + return ok + } +} + +func not(fn func(types.Container) bool) func(types.Container) bool { + return func(container types.Container) bool { + ok := fn(container) + return !ok + } +} diff --git a/cli/command/network/inspect.go b/cli/command/network/inspect.go index 89c1d6c0ca..33dba10df7 100644 --- a/cli/command/network/inspect.go +++ b/cli/command/network/inspect.go @@ -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/inspect" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/docker/api/types" @@ -28,6 +29,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command { opts.names = args return runInspect(dockerCli, opts) }, + ValidArgsFunction: completion.NetworkNames(dockerCli), } cmd.Flags().StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp) diff --git a/cli/command/network/list.go b/cli/command/network/list.go index fa6abcb3e8..bd75d66056 100644 --- a/cli/command/network/list.go +++ b/cli/command/network/list.go @@ -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" @@ -32,6 +33,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(dockerCli, options) }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/network/remove.go b/cli/command/network/remove.go index 6ee28b053b..ee96fd614c 100644 --- a/cli/command/network/remove.go +++ b/cli/command/network/remove.go @@ -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/docker/api/types" "github.com/docker/docker/errdefs" "github.com/spf13/cobra" @@ -26,6 +27,7 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runRemove(dockerCli, args, &opts) }, + ValidArgsFunction: completion.NetworkNames(dockerCli), } flags := cmd.Flags() diff --git a/cli/command/node/list.go b/cli/command/node/list.go index 6fee9a9b11..99d0a428fe 100644 --- a/cli/command/node/list.go +++ b/cli/command/node/list.go @@ -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" @@ -31,6 +32,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(dockerCli, options) }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs") diff --git a/cli/command/node/ps.go b/cli/command/node/ps.go index 2450e6af39..28f90a387d 100644 --- a/cli/command/node/ps.go +++ b/cli/command/node/ps.go @@ -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/idresolver" "github.com/docker/cli/cli/command/task" "github.com/docker/cli/opts" @@ -40,6 +41,7 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command { return runPs(dockerCli, options) }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output") diff --git a/cli/command/plugin/list.go b/cli/command/plugin/list.go index 0a37fb6538..2b26b119d4 100644 --- a/cli/command/plugin/list.go +++ b/cli/command/plugin/list.go @@ -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" @@ -31,6 +32,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(dockerCli, options) }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/secret/cmd.go b/cli/command/secret/cmd.go index b7e38e3073..ec0643b054 100644 --- a/cli/command/secret/cmd.go +++ b/cli/command/secret/cmd.go @@ -1,10 +1,11 @@ package secret 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" ) // NewSecretCommand returns a cobra command for `secret` subcommands @@ -27,3 +28,18 @@ func NewSecretCommand(dockerCli command.Cli) *cobra.Command { ) return cmd } + +// completeNames offers completion for swarm secrets +func completeNames(dockerCli command.Cli) completion.ValidArgsFn { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + list, err := dockerCli.Client().SecretList(cmd.Context(), types.SecretListOptions{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var names []string + for _, secret := range list { + names = append(names, secret.ID) + } + return names, cobra.ShellCompDirectiveNoFileComp + } +} diff --git a/cli/command/secret/inspect.go b/cli/command/secret/inspect.go index 0a8df1bcea..7df5fdafb7 100644 --- a/cli/command/secret/inspect.go +++ b/cli/command/secret/inspect.go @@ -28,6 +28,9 @@ func newSecretInspectCommand(dockerCli command.Cli) *cobra.Command { opts.names = args return runSecretInspect(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) diff --git a/cli/command/secret/ls.go b/cli/command/secret/ls.go index c1f11f88d4..6d8693875c 100644 --- a/cli/command/secret/ls.go +++ b/cli/command/secret/ls.go @@ -31,6 +31,9 @@ func newSecretListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runSecretList(dockerCli, options) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completeNames(dockerCli)(cmd, args, toComplete) + }, } flags := cmd.Flags() diff --git a/cli/command/secret/remove.go b/cli/command/secret/remove.go index bdf47b77fa..b22dd76d78 100644 --- a/cli/command/secret/remove.go +++ b/cli/command/secret/remove.go @@ -27,6 +27,9 @@ func newSecretRemoveCommand(dockerCli command.Cli) *cobra.Command { } return runSecretRemove(dockerCli, opts) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completeNames(dockerCli)(cmd, args, toComplete) + }, } } diff --git a/cli/command/service/cmd.go b/cli/command/service/cmd.go index 69472c52b2..2c0b8fca9d 100644 --- a/cli/command/service/cmd.go +++ b/cli/command/service/cmd.go @@ -1,10 +1,11 @@ package service 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" ) // NewServiceCommand returns a cobra command for `service` subcommands @@ -32,3 +33,18 @@ func NewServiceCommand(dockerCli command.Cli) *cobra.Command { ) return cmd } + +// CompletionFn offers completion for swarm services +func CompletionFn(dockerCli command.Cli) completion.ValidArgsFn { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + list, err := dockerCli.Client().ServiceList(cmd.Context(), types.ServiceListOptions{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var names []string + for _, service := range list { + names = append(names, service.ID) + } + return names, cobra.ShellCompDirectiveNoFileComp + } +} diff --git a/cli/command/service/inspect.go b/cli/command/service/inspect.go index 59e4c03cd2..16df0c1003 100644 --- a/cli/command/service/inspect.go +++ b/cli/command/service/inspect.go @@ -35,6 +35,9 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command { } return runInspect(dockerCli, opts) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return CompletionFn(dockerCli)(cmd, args, toComplete) + }, } flags := cmd.Flags() diff --git a/cli/command/service/list.go b/cli/command/service/list.go index 842205a579..ce6d1dc9e7 100644 --- a/cli/command/service/list.go +++ b/cli/command/service/list.go @@ -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" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/opts" @@ -32,6 +33,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(dockerCli, options) }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/service/logs.go b/cli/command/service/logs.go index 22ced8f7b7..ce18b124af 100644 --- a/cli/command/service/logs.go +++ b/cli/command/service/logs.go @@ -48,6 +48,9 @@ func newLogsCommand(dockerCli command.Cli) *cobra.Command { return runLogs(dockerCli, &opts) }, Annotations: map[string]string{"version": "1.29"}, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return CompletionFn(dockerCli)(cmd, args, toComplete) + }, } flags := cmd.Flags() diff --git a/cli/command/service/ps.go b/cli/command/service/ps.go index d130404710..ce49fce1f5 100644 --- a/cli/command/service/ps.go +++ b/cli/command/service/ps.go @@ -37,6 +37,9 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command { options.services = args return runPS(dockerCli, options) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return CompletionFn(dockerCli)(cmd, args, toComplete) + }, } flags := cmd.Flags() flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs") diff --git a/cli/command/service/remove.go b/cli/command/service/remove.go index ee810b0388..bdf1a593a8 100644 --- a/cli/command/service/remove.go +++ b/cli/command/service/remove.go @@ -21,6 +21,9 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runRemove(dockerCli, args) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return CompletionFn(dockerCli)(cmd, args, toComplete) + }, } cmd.Flags() diff --git a/cli/command/service/rollback.go b/cli/command/service/rollback.go index 2196815cd8..5b9a863e20 100644 --- a/cli/command/service/rollback.go +++ b/cli/command/service/rollback.go @@ -22,6 +22,9 @@ func newRollbackCommand(dockerCli command.Cli) *cobra.Command { return runRollback(dockerCli, options, args[0]) }, Annotations: map[string]string{"version": "1.31"}, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return CompletionFn(dockerCli)(cmd, args, toComplete) + }, } flags := cmd.Flags() diff --git a/cli/command/service/scale.go b/cli/command/service/scale.go index 1184241a48..4bc00e5050 100644 --- a/cli/command/service/scale.go +++ b/cli/command/service/scale.go @@ -28,6 +28,9 @@ func newScaleCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runScale(dockerCli, options, args) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return CompletionFn(dockerCli)(cmd, args, toComplete) + }, } flags := cmd.Flags() diff --git a/cli/command/service/update.go b/cli/command/service/update.go index ac4db918aa..47d3b20cf0 100644 --- a/cli/command/service/update.go +++ b/cli/command/service/update.go @@ -33,6 +33,9 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runUpdate(dockerCli, cmd.Flags(), options, args[0]) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return CompletionFn(dockerCli)(cmd, args, toComplete) + }, } flags := cmd.Flags() diff --git a/cli/command/stack/cmd.go b/cli/command/stack/cmd.go index 3503454312..ff55262bf3 100644 --- a/cli/command/stack/cmd.go +++ b/cli/command/stack/cmd.go @@ -5,6 +5,8 @@ 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/stack/swarm" "github.com/spf13/cobra" ) @@ -42,3 +44,18 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command { flags.MarkDeprecated("orchestrator", "option will be ignored") return cmd } + +// completeNames offers completion for swarm stacks +func completeNames(dockerCli command.Cli) completion.ValidArgsFn { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + list, err := swarm.GetStacks(dockerCli) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var names []string + for _, stack := range list { + names = append(names, stack.Name) + } + return names, cobra.ShellCompDirectiveNoFileComp + } +} diff --git a/cli/command/stack/config.go b/cli/command/stack/config.go index c6bf74f1e3..354c35625a 100644 --- a/cli/command/stack/config.go +++ b/cli/command/stack/config.go @@ -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/stack/loader" "github.com/docker/cli/cli/command/stack/options" composeLoader "github.com/docker/cli/cli/compose/loader" @@ -34,6 +35,7 @@ func newConfigCommand(dockerCli command.Cli) *cobra.Command { _, err = fmt.Fprintf(dockerCli.Out(), "%s", cfg) return err }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index 9adfaf96bc..e02524ec1e 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -30,6 +30,9 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command { } return RunDeploy(dockerCli, cmd.Flags(), config, opts) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completeNames(dockerCli)(cmd, args, toComplete) + }, } flags := cmd.Flags() diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index 866961b2e5..40c1898cc6 100644 --- a/cli/command/stack/list.go +++ b/cli/command/stack/list.go @@ -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/stack/formatter" "github.com/docker/cli/cli/command/stack/options" "github.com/docker/cli/cli/command/stack/swarm" @@ -24,6 +25,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return RunList(cmd, dockerCli, opts) }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/stack/ps.go b/cli/command/stack/ps.go index cbe992cfa5..84c250ea91 100644 --- a/cli/command/stack/ps.go +++ b/cli/command/stack/ps.go @@ -25,6 +25,9 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command { } return RunPs(dockerCli, cmd.Flags(), opts) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completeNames(dockerCli)(cmd, args, toComplete) + }, } flags := cmd.Flags() flags.BoolVar(&opts.NoTrunc, "no-trunc", false, "Do not truncate output") diff --git a/cli/command/stack/remove.go b/cli/command/stack/remove.go index b326208774..9e55e65a7e 100644 --- a/cli/command/stack/remove.go +++ b/cli/command/stack/remove.go @@ -24,6 +24,9 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command { } return RunRemove(dockerCli, cmd.Flags(), opts) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completeNames(dockerCli)(cmd, args, toComplete) + }, } return cmd } diff --git a/cli/command/stack/services.go b/cli/command/stack/services.go index 456a1fad04..cc0bdc5e2b 100644 --- a/cli/command/stack/services.go +++ b/cli/command/stack/services.go @@ -32,6 +32,9 @@ func newServicesCommand(dockerCli command.Cli) *cobra.Command { } return RunServices(dockerCli, cmd.Flags(), opts) }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completeNames(dockerCli)(cmd, args, toComplete) + }, } flags := cmd.Flags() flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs") diff --git a/cli/command/swarm/ca.go b/cli/command/swarm/ca.go index 62fba6a4cd..8793a1c9be 100644 --- a/cli/command/swarm/ca.go +++ b/cli/command/swarm/ca.go @@ -8,6 +8,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/swarm/progress" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/jsonmessage" @@ -39,6 +40,7 @@ func newCACommand(dockerCli command.Cli) *cobra.Command { "version": "1.30", "swarm": "manager", }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/swarm/init.go b/cli/command/swarm/init.go index 333bf34039..8792e3ff50 100644 --- a/cli/command/swarm/init.go +++ b/cli/command/swarm/init.go @@ -8,6 +8,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/swarm" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -43,6 +44,7 @@ func newInitCommand(dockerCli command.Cli) *cobra.Command { "version": "1.24", "swarm": "", // swarm init does not require swarm to be active, and is always available on API 1.24 and up }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/swarm/leave.go b/cli/command/swarm/leave.go index 782f46c325..586e59aa94 100644 --- a/cli/command/swarm/leave.go +++ b/cli/command/swarm/leave.go @@ -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/spf13/cobra" ) @@ -27,6 +28,7 @@ func newLeaveCommand(dockerCli command.Cli) *cobra.Command { "version": "1.24", "swarm": "active", }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/swarm/unlock.go b/cli/command/swarm/unlock.go index 369852dfb2..12db5d884b 100644 --- a/cli/command/swarm/unlock.go +++ b/cli/command/swarm/unlock.go @@ -9,6 +9,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/streams" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" @@ -28,6 +29,7 @@ func newUnlockCommand(dockerCli command.Cli) *cobra.Command { "version": "1.24", "swarm": "manager", }, + ValidArgsFunction: completion.NoComplete, } return cmd diff --git a/cli/command/swarm/unlock_key.go b/cli/command/swarm/unlock_key.go index 672d333fa8..b3e051a18a 100644 --- a/cli/command/swarm/unlock_key.go +++ b/cli/command/swarm/unlock_key.go @@ -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/swarm" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -31,6 +32,7 @@ func newUnlockKeyCommand(dockerCli command.Cli) *cobra.Command { "version": "1.24", "swarm": "manager", }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/swarm/update.go b/cli/command/swarm/update.go index c2bc6b3352..dc72fc744e 100644 --- a/cli/command/swarm/update.go +++ b/cli/command/swarm/update.go @@ -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/docker/api/types/swarm" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -32,6 +33,7 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command { "version": "1.24", "swarm": "manager", }, + ValidArgsFunction: completion.NoComplete, } cmd.Flags().BoolVar(&opts.autolock, flagAutolock, false, "Change manager autolocking setting (true|false)") diff --git a/cli/command/system/df.go b/cli/command/system/df.go index bb1bf2cf43..150e0d0e0b 100644 --- a/cli/command/system/df.go +++ b/cli/command/system/df.go @@ -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" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/docker/api/types" @@ -27,7 +28,8 @@ func newDiskUsageCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runDiskUsage(dockerCli, opts) }, - Annotations: map[string]string{"version": "1.25"}, + Annotations: map[string]string{"version": "1.25"}, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/system/dial_stdio.go b/cli/command/system/dial_stdio.go index 2385c14567..6bfef787a6 100644 --- a/cli/command/system/dial_stdio.go +++ b/cli/command/system/dial_stdio.go @@ -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/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -22,6 +23,7 @@ func newDialStdioCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runDialStdio(dockerCli) }, + ValidArgsFunction: completion.NoComplete, } return cmd } diff --git a/cli/command/system/events.go b/cli/command/system/events.go index 643cde070b..7a632d8479 100644 --- a/cli/command/system/events.go +++ b/cli/command/system/events.go @@ -11,6 +11,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/cli/templates" "github.com/docker/docker/api/types" @@ -36,6 +37,7 @@ func NewEventsCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runEvents(dockerCli, &options) }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/system/info.go b/cli/command/system/info.go index 2a412c99fc..e1f0ac5c55 100644 --- a/cli/command/system/info.go +++ b/cli/command/system/info.go @@ -11,6 +11,7 @@ import ( "github.com/docker/cli/cli" pluginmanager "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/debug" "github.com/docker/cli/templates" "github.com/docker/docker/api/types" @@ -57,6 +58,7 @@ func NewInfoCommand(dockerCli command.Cli) *cobra.Command { Annotations: map[string]string{ "category-top": "12", }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/system/prune.go b/cli/command/system/prune.go index c9de08d3a8..4e1042ed6e 100644 --- a/cli/command/system/prune.go +++ b/cli/command/system/prune.go @@ -9,6 +9,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/builder" + "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/container" "github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/command/network" @@ -40,7 +41,8 @@ func newPruneCommand(dockerCli command.Cli) *cobra.Command { options.pruneBuildCache = versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31") return runPrune(dockerCli, options) }, - Annotations: map[string]string{"version": "1.25"}, + Annotations: map[string]string{"version": "1.25"}, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/system/version.go b/cli/command/system/version.go index 1192a62107..d573681ff7 100644 --- a/cli/command/system/version.go +++ b/cli/command/system/version.go @@ -10,6 +10,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/tabwriter" "github.com/docker/cli/cli/version" "github.com/docker/cli/templates" @@ -97,6 +98,7 @@ func NewVersionCommand(dockerCli command.Cli) *cobra.Command { Annotations: map[string]string{ "category-top": "10", }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/volume/inspect.go b/cli/command/volume/inspect.go index c2fbc39c8c..4bf35b26b8 100644 --- a/cli/command/volume/inspect.go +++ b/cli/command/volume/inspect.go @@ -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/inspect" flagsHelper "github.com/docker/cli/cli/flags" "github.com/spf13/cobra" @@ -26,6 +27,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command { opts.names = args return runInspect(dockerCli, opts) }, + ValidArgsFunction: completion.VolumeNames(dockerCli), } cmd.Flags().StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp) diff --git a/cli/command/volume/list.go b/cli/command/volume/list.go index 7b5a27ac8e..2c04675f21 100644 --- a/cli/command/volume/list.go +++ b/cli/command/volume/list.go @@ -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" @@ -30,6 +31,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runList(dockerCli, options) }, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/volume/prune.go b/cli/command/volume/prune.go index 8e48eb973a..8f95f499f0 100644 --- a/cli/command/volume/prune.go +++ b/cli/command/volume/prune.go @@ -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" units "github.com/docker/go-units" "github.com/spf13/cobra" @@ -35,7 +36,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.25"}, + Annotations: map[string]string{"version": "1.25"}, + ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() diff --git a/cli/command/volume/remove.go b/cli/command/volume/remove.go index 66df3b8b50..a2abb5947c 100644 --- a/cli/command/volume/remove.go +++ b/cli/command/volume/remove.go @@ -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/pkg/errors" "github.com/spf13/cobra" ) @@ -31,6 +32,7 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command { opts.volumes = args return runRemove(dockerCli, &opts) }, + ValidArgsFunction: completion.VolumeNames(dockerCli), } flags := cmd.Flags() diff --git a/cli/context/store/store.go b/cli/context/store/store.go index 5ede61fd83..19ad980a47 100644 --- a/cli/context/store/store.go +++ b/cli/context/store/store.go @@ -118,6 +118,19 @@ func (s *store) List() ([]Metadata, error) { return s.meta.list() } +// Names return Metadata names for a Lister +func Names(s Lister) ([]string, error) { + list, err := s.List() + if err != nil { + return nil, err + } + var names []string + for _, item := range list { + names = append(names, item.Name) + } + return names, nil +} + func (s *store) CreateOrUpdate(meta Metadata) error { return s.meta.createOrUpdate(meta) } diff --git a/cmd/docker/completions.go b/cmd/docker/completions.go new file mode 100644 index 0000000000..15e078f8de --- /dev/null +++ b/cmd/docker/completions.go @@ -0,0 +1,27 @@ +package main + +import ( + "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/context/store" + "github.com/spf13/cobra" +) + +func registerCompletionFuncForGlobalFlags(dockerCli *command.DockerCli, cmd *cobra.Command) { + cmd.RegisterFlagCompletionFunc( + "context", + func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + names, err := store.Names(dockerCli.ContextStore()) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + return names, cobra.ShellCompDirectiveNoFileComp + }, + ) + cmd.RegisterFlagCompletionFunc( + "log-level", + func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + values := []string{"debug", "info", "warn", "error", "fatal"} + return values, cobra.ShellCompDirectiveNoFileComp + }, + ) +} diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index a1ae91c19f..fb0ff3d55d 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -47,10 +47,15 @@ func newDockerCommand(dockerCli *command.DockerCli) *cli.TopLevelCommand { }, Version: fmt.Sprintf("%s, build %s", version.Version, version.GitCommit), DisableFlagsInUseLine: true, + CompletionOptions: cobra.CompletionOptions{ + DisableDefaultCmd: false, + HiddenDefaultCmd: true, + DisableDescriptions: true, + }, } opts, flags, helpCmd = cli.SetupRootCommand(cmd) + registerCompletionFuncForGlobalFlags(dockerCli, cmd) flags.BoolP("version", "v", false, "Print version information and quit") - setFlagErrorFunc(dockerCli, cmd) setupHelpCommand(dockerCli, cmd, helpCmd) @@ -95,13 +100,10 @@ func setupHelpCommand(dockerCli command.Cli, rootCmd, helpCmd *cobra.Command) { if len(args) > 0 { helpcmd, err := pluginmanager.PluginRunCommand(dockerCli, args[0], rootCmd) if err == nil { - err = helpcmd.Run() - if err != nil { - return err - } + return helpcmd.Run() } if !pluginmanager.IsNotFound(err) { - return err + return errors.Errorf("unknown help topic: %v", strings.Join(args, " ")) } } if origRunE != nil { @@ -129,25 +131,13 @@ func tryRunPluginHelp(dockerCli command.Cli, ccmd *cobra.Command, cargs []string func setHelpFunc(dockerCli command.Cli, cmd *cobra.Command) { defaultHelpFunc := cmd.HelpFunc() cmd.SetHelpFunc(func(ccmd *cobra.Command, args []string) { - // Add a stub entry for every plugin so they are - // included in the help output and so that - // `tryRunPluginHelp` can find them or if we fall - // through they will be included in the default help - // output. - if err := pluginmanager.AddPluginCommandStubs(dockerCli, ccmd.Root()); err != nil { - ccmd.Println(err) - return - } - - if len(args) >= 1 { + if ccmd.Annotations[pluginmanager.CommandAnnotationPlugin] == "true" { err := tryRunPluginHelp(dockerCli, ccmd, args) - if err == nil { // Successfully ran the plugin - return - } if !pluginmanager.IsNotFound(err) { ccmd.Println(err) - return } + cmd.PrintErrf("unknown help topic: %v\n", ccmd.Name()) + return } if err := isSupported(ccmd, dockerCli); err != nil { @@ -163,7 +153,7 @@ func setHelpFunc(dockerCli command.Cli, cmd *cobra.Command) { }) } -func setValidateArgs(dockerCli *command.DockerCli, cmd *cobra.Command) { +func setValidateArgs(dockerCli command.Cli, cmd *cobra.Command) { // The Args is handled by ValidateArgs in cobra, which does not allows a pre-hook. // As a result, here we replace the existing Args validation func to a wrapper, // where the wrapper will check to see if the feature is supported or not. @@ -228,13 +218,19 @@ func runDocker(dockerCli *command.DockerCli) error { return err } + err = pluginmanager.AddPluginCommandStubs(dockerCli, cmd) + if err != nil { + return err + } + args, os.Args, err = processAliases(dockerCli, cmd, args, os.Args) if err != nil { return err } if len(args) > 0 { - if _, _, err := cmd.Find(args); err != nil { + command, _, err := cmd.Find(args) + if err != nil || command.Annotations[pluginmanager.CommandAnnotationPlugin] == "true" { err := tryPluginRun(dockerCli, cmd, args[0]) if !pluginmanager.IsNotFound(err) { return err diff --git a/cmd/docker/docker_test.go b/cmd/docker/docker_test.go index fc1c5829bd..084626252e 100644 --- a/cmd/docker/docker_test.go +++ b/cmd/docker/docker_test.go @@ -42,6 +42,7 @@ func runCliCommand(t *testing.T, r io.ReadCloser, w io.Writer, args ...string) e cli, err := command.NewDockerCli(command.WithInputStream(r), command.WithCombinedStreams(w)) assert.NilError(t, err) tcmd := newDockerCommand(cli) + tcmd.SetArgs(args) cmd, _, err := tcmd.HandleGlobalFlags() assert.NilError(t, err) diff --git a/e2e/cli-plugins/run_test.go b/e2e/cli-plugins/run_test.go index e3290b81fa..2a2f318f46 100644 --- a/e2e/cli-plugins/run_test.go +++ b/e2e/cli-plugins/run_test.go @@ -83,7 +83,7 @@ func TestHelpBad(t *testing.T) { res := icmd.RunCmd(run("help", "badmeta")) res.Assert(t, icmd.Expected{ - ExitCode: 1, + ExitCode: 0, Out: icmd.None, }) golden.Assert(t, res.Stderr(), "docker-help-badmeta-err.golden") @@ -105,13 +105,13 @@ func TestBadHelp(t *testing.T) { Err: icmd.None, }) // Short -h should be the same, modulo the deprecation message - exp := shortHFlagDeprecated + res.Stdout() + usage := res.Stdout() res = icmd.RunCmd(run("badmeta", "-h")) res.Assert(t, icmd.Expected{ ExitCode: 0, // This should be identical to the --help case above - Out: exp, - Err: icmd.None, + Out: usage, + Err: shortHFlagDeprecated, }) } diff --git a/e2e/context/context_test.go b/e2e/context/context_test.go index 649e4f768c..d286ff2a05 100644 --- a/e2e/context/context_test.go +++ b/e2e/context/context_test.go @@ -11,7 +11,7 @@ import ( func TestContextList(t *testing.T) { cmd := icmd.Command("docker", "context", "ls") - cmd.Env = append(cmd.Env, "DOCKER_CONFIG=./testdata/test-dockerconfig") + cmd.Env = append(cmd.Env, "DOCKER_CONFIG=testdata/test-dockerconfig") result := icmd.RunCmd(cmd).Assert(t, icmd.Expected{ Err: icmd.None, ExitCode: 0,