Skip to content

Commit

Permalink
Adopt Cobra completion v2 to support completion by CLI plugins
Browse files Browse the repository at this point in the history
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
  • Loading branch information
ndeloof committed Mar 15, 2022
1 parent 9c8f46f commit 09eeb1f
Show file tree
Hide file tree
Showing 37 changed files with 280 additions and 15 deletions.
4 changes: 3 additions & 1 deletion cli-plugins/manager/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const (

// Metadata provided by the plugin.
type Metadata struct {
// SchemaVersion describes the version of this struct. Mandatory, must be "0.1.0"
// SchemaVersion describes the version of this struct. Mandatory, must be "0.2.0"
SchemaVersion string `json:",omitempty"`
// Vendor is the name of the plugin vendor. Mandatory
Vendor string `json:",omitempty"`
Expand All @@ -25,4 +25,6 @@ type Metadata struct {
// Experimental specifies whether the plugin is experimental.
// Deprecated: experimental features are now always enabled in the CLI
Experimental bool `json:",omitempty"`
// Completions specifies whether the plugin supports command completion
Completions bool `json:",omitempty"`
}
4 changes: 2 additions & 2 deletions cli-plugins/manager/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ func newPlugin(c Candidate, rootcmd *cobra.Command) (Plugin, error) {
p.Err = wrapAsPluginError(err, "invalid metadata")
return p, nil
}
if p.Metadata.SchemaVersion != "0.1.0" {
p.Err = NewPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
if p.Metadata.SchemaVersion != "0.1.0" && p.Metadata.SchemaVersion != "0.2.0" {
p.Err = NewPluginError("plugin SchemaVersion %q is not valid", p.Metadata.SchemaVersion)
return p, nil
}
if p.Metadata.Vendor == "" {
Expand Down
16 changes: 15 additions & 1 deletion 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: false,
DisableDescriptions: true,
},
}
opts, flags := cli.SetupPluginRootCommand(cmd)

Expand Down Expand Up @@ -166,5 +171,14 @@ func RunningStandalone() bool {
if os.Getenv(manager.ReexecEnvvar) != "" {
return false
}
return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName
if len(os.Args) < 2 {
return true
}
rootCommands := []string{manager.MetadataSubcommandName, cobra.ShellCompRequestCmd, cobra.ShellCompNoDescRequestCmd}
for _, cmd := range rootCommands {
if os.Args[1] == cmd {
return true
}
}
return false
}
92 changes: 92 additions & 0 deletions cli/command/completions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package command

import (
"github.com/docker/docker/api/types/filters"
"os"

"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
)

// ShowContainerIDs if true will offer completion on container IDs, not just names
var ShowContainerIDs = os.Getenv("DOCKER_COMPLETION_SHOW_CONTAINER_IDS") == "yes"

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

// ContainerNamesCompletionFn 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 ContainerNamesCompletionFn(dockerCli Cli, all bool, filters ...func(types.Container) bool) CompletionFn {
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
}
var names []string
for _, container := range list {
ok := true
for _, fn := range filters {
if !fn(container) {
ok = false
break
}
}
if !ok {
continue
}
if ShowContainerIDs {
names = append(names, container.ID)
}
names = append(names, StripNamePrefix(container.Names)...)
}
return names, cobra.ShellCompDirectiveNoFileComp
}
}

// ImagesCompletionFn offers completion for images present within the local store
func ImagesCompletionFn(dockerCli Cli) CompletionFn {
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
}
}

// VolumesCompletionFn offers completion for volumes
func VolumesCompletionFn(dockerCli Cli) CompletionFn {
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
}
}

// NetworksCompletionFn offers completion for networks
func NetworksCompletionFn(dockerCli Cli) CompletionFn {
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
}
}
1 change: 1 addition & 0 deletions cli/command/container/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func NewAttachCommand(dockerCli command.Cli) *cobra.Command {
opts.container = args[0]
return runAttach(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, false),
}

flags := cmd.Flags()
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
}
return runCommit(dockerCli, &options)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, false),
}

flags := cmd.Flags()
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
}
return runCreate(dockerCli, cmd.Flags(), &opts, copts)
},
ValidArgsFunction: command.ImagesCompletionFn(dockerCli),
}

flags := cmd.Flags()
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func NewDiffCommand(dockerCli command.Cli) *cobra.Command {
opts.container = args[0]
return runDiff(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, false),
}
}

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

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
Expand Down Expand Up @@ -52,6 +53,7 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
options.Command = args[1:]
return RunExec(dockerCli, options)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, false),
}

flags := cmd.Flags()
Expand All @@ -70,6 +72,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
1 change: 1 addition & 0 deletions cli/command/container/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func NewExportCommand(dockerCli command.Cli) *cobra.Command {
opts.container = args[0]
return runExport(dockerCli, opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, true),
}

flags := cmd.Flags()
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
opts.refs = args
return runInspect(dockerCli, opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, true),
}

flags := cmd.Flags()
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/kill.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command {
opts.containers = args
return runKill(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, false),
}

flags := cmd.Flags()
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func NewLogsCommand(dockerCli command.Cli) *cobra.Command {
opts.container = args[0]
return runLogs(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, true),
}

flags := cmd.Flags()
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/pause.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func NewPauseCommand(dockerCli command.Cli) *cobra.Command {
opts.containers = args
return runPause(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, false),
}
}

Expand Down
1 change: 1 addition & 0 deletions cli/command/container/port.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func NewPortCommand(dockerCli command.Cli) *cobra.Command {
}
return runPort(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, false),
}
return cmd
}
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/rename.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func NewRenameCommand(dockerCli command.Cli) *cobra.Command {
opts.newName = args[1]
return runRename(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, true),
}
return cmd
}
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
opts.nSecondsChanged = cmd.Flags().Changed("time")
return runRestart(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, true),
}

flags := cmd.Flags()
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/rm.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
opts.containers = args
return runRm(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, true),
}

flags := cmd.Flags()
Expand Down
15 changes: 15 additions & 0 deletions cli/command/container/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"os"
"runtime"
"strings"
"syscall"
Expand Down Expand Up @@ -44,6 +45,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
}
return runRun(dockerCli, cmd.Flags(), &opts, copts)
},
ValidArgsFunction: command.ImagesCompletionFn(dockerCli),
}

flags := cmd.Flags()
Expand All @@ -65,6 +67,19 @@ 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 // _filedir
},
)
return cmd
}

Expand Down
1 change: 1 addition & 0 deletions cli/command/container/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func NewStartCommand(dockerCli command.Cli) *cobra.Command {
opts.Containers = args
return RunStart(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, true),
}

flags := cmd.Flags()
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func NewStatsCommand(dockerCli command.Cli) *cobra.Command {
opts.containers = args
return runStats(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, false),
}

flags := cmd.Flags()
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func NewStopCommand(dockerCli command.Cli) *cobra.Command {
opts.timeChanged = cmd.Flags().Changed("time")
return runStop(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, false),
}

flags := cmd.Flags()
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/top.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func NewTopCommand(dockerCli command.Cli) *cobra.Command {
opts.args = args[1:]
return runTop(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, false),
}

flags := cmd.Flags()
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/unpause.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func NewUnpauseCommand(dockerCli command.Cli) *cobra.Command {
opts.containers = args
return runUnpause(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, false),
}
return cmd
}
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func NewUpdateCommand(dockerCli command.Cli) *cobra.Command {
options.nFlag = cmd.Flags().NFlag()
return runUpdate(dockerCli, &options)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, true),
}

flags := cmd.Flags()
Expand Down
1 change: 1 addition & 0 deletions cli/command/container/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func NewWaitCommand(dockerCli command.Cli) *cobra.Command {
opts.containers = args
return runWait(dockerCli, &opts)
},
ValidArgsFunction: command.ContainerNamesCompletionFn(dockerCli, false),
}

return cmd
Expand Down
3 changes: 2 additions & 1 deletion cli/command/formatter/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"time"

"github.com/docker/cli/cli/command"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
Expand Down Expand Up @@ -125,7 +126,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 := command.StripNamePrefix(c.c.Names)
if c.trunc {
for _, name := range names {
if len(strings.Split(name, "/")) == 1 {
Expand Down

0 comments on commit 09eeb1f

Please sign in to comment.