Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cobra completion v2 with CLI plugin support #3429

Merged
merged 1 commit into from
May 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
}
thaJeztah marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does _filedir mean here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default directive will list files in current dir as completion option, which is relevant for this flag

},
)

return cmd
}

Expand Down