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 Feb 28, 2022
1 parent 3d4a8dd commit 5a65670
Show file tree
Hide file tree
Showing 32 changed files with 206 additions and 14 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
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: false,
DisableDescriptions: true,
},
}
opts, flags := cli.SetupPluginRootCommand(cmd)

Expand Down
50 changes: 50 additions & 0 deletions cli/command/completions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package command

import (
"os"

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

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) CompletionFn {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCli.Client().ContainerList(cmd.Context(), types.ContainerListOptions{
All: true,
})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, container := range list {
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
}
}
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),
}

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),
}

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 @@ -55,6 +55,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),
}
}

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),
}

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),
}

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),
}

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),
}

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),
}

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),
}
}

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),
}
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),
}
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),
}

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),
}

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 @@ -64,6 +66,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),
}

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),
}

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),
}

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),
}

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),
}
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),
}

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),
}

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
9 changes: 0 additions & 9 deletions cli/command/formatter/custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
10 changes: 10 additions & 0 deletions cli/command/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,13 @@ func StringSliceReplaceAt(s, old, new []string, requireIndex int) ([]string, boo
out = append(out, s[idx+len(old):]...)
return out, true
}

// 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
}
13 changes: 13 additions & 0 deletions cli/context/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,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)
}
Expand Down

0 comments on commit 5a65670

Please sign in to comment.