context: add shell-completion for context-names
For now, these are not exported and included in the cli/commands/contexts package; a copy of this also lives in cmd/docker, but we need to find a good place for these completions, as some of them bring in additional dependencies. Commands that accept multiple arguments provide completion, but removing duplicates: docker context inspect<TAB> default desktop-linux (current) production tcd docker context inspec default<TAB> desktop-linux (current) production tcd docker context inspect default tcd<TAB> desktop-linux (current) production For "context export", we provide completion for the first argument, after which file-completion is provided: # provides context names completion for the first argument docker context export production<TAB> default desktop-linux (current) production tcd # then provides completion for filenames docker context export desktop-linux<TAB> build/ man/ TESTING.md cli/ docker.Makefile go.mod ... Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
b8857225a0
commit
6fd72c6333
46
cli/command/context/completion.go
Normal file
46
cli/command/context/completion.go
Normal file
@ -0,0 +1,46 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.22
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/docker/cli/cli/context/store"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type contextProvider interface {
|
||||
ContextStore() store.Store
|
||||
CurrentContext() string
|
||||
}
|
||||
|
||||
// completeContextNames implements shell completion for context-names.
|
||||
//
|
||||
// FIXME(thaJeztah): export, and remove duplicate of this function in cmd/docker.
|
||||
func completeContextNames(dockerCLI contextProvider, limit int, withFileComp bool) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
|
||||
return func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if limit > 0 && len(args) >= limit {
|
||||
if withFileComp {
|
||||
// Provide file/path completion after context name (for "docker context export")
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
// TODO(thaJeztah): implement function similar to [store.Names] to (also) include descriptions.
|
||||
names, _ := store.Names(dockerCLI.ContextStore())
|
||||
out := make([]string, 0, len(names))
|
||||
for _, name := range names {
|
||||
if slices.Contains(args, name) {
|
||||
// Already completed
|
||||
continue
|
||||
}
|
||||
if name == dockerCLI.CurrentContext() {
|
||||
name += "\tcurrent"
|
||||
}
|
||||
out = append(out, name)
|
||||
}
|
||||
return out, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
80
cli/command/context/completion_test.go
Normal file
80
cli/command/context/completion_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/context/store"
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
type fakeContextProvider struct {
|
||||
contextStore store.Store
|
||||
}
|
||||
|
||||
func (c *fakeContextProvider) ContextStore() store.Store {
|
||||
return c.contextStore
|
||||
}
|
||||
|
||||
func (*fakeContextProvider) CurrentContext() string {
|
||||
return "default"
|
||||
}
|
||||
|
||||
type fakeContextStore struct {
|
||||
store.Store
|
||||
names []string
|
||||
}
|
||||
|
||||
func (f fakeContextStore) List() (c []store.Metadata, _ error) {
|
||||
for _, name := range f.names {
|
||||
c = append(c, store.Metadata{Name: name})
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func TestCompleteContextNames(t *testing.T) {
|
||||
allNames := []string{"context-b", "context-c", "context-a"}
|
||||
cli := &fakeContextProvider{
|
||||
contextStore: fakeContextStore{
|
||||
names: allNames,
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("with limit", func(t *testing.T) {
|
||||
compFunc := completeContextNames(cli, 1, false)
|
||||
values, directives := compFunc(nil, nil, "")
|
||||
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp))
|
||||
assert.Check(t, is.DeepEqual(values, allNames))
|
||||
|
||||
values, directives = compFunc(nil, []string{"context-c"}, "")
|
||||
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp))
|
||||
assert.Check(t, is.Len(values, 0))
|
||||
})
|
||||
|
||||
t.Run("with limit and file completion", func(t *testing.T) {
|
||||
compFunc := completeContextNames(cli, 1, true)
|
||||
values, directives := compFunc(nil, nil, "")
|
||||
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp))
|
||||
assert.Check(t, is.DeepEqual(values, allNames))
|
||||
|
||||
values, directives = compFunc(nil, []string{"context-c"}, "")
|
||||
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveDefault), "should provide filenames completion after limit")
|
||||
assert.Check(t, is.Len(values, 0))
|
||||
})
|
||||
|
||||
t.Run("without limits", func(t *testing.T) {
|
||||
compFunc := completeContextNames(cli, -1, false)
|
||||
values, directives := compFunc(nil, []string{"context-c"}, "")
|
||||
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp))
|
||||
assert.Check(t, is.DeepEqual(values, []string{"context-b", "context-a"}), "should not contain already completed")
|
||||
|
||||
values, directives = compFunc(nil, []string{"context-c", "context-a"}, "")
|
||||
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp))
|
||||
assert.Check(t, is.DeepEqual(values, []string{"context-b"}), "should not contain already completed")
|
||||
|
||||
values, directives = compFunc(nil, []string{"context-c", "context-a", "context-b"}, "")
|
||||
assert.Check(t, is.Equal(directives, cobra.ShellCompDirectiveNoFileComp), "should provide filenames completion after limit")
|
||||
assert.Check(t, is.Len(values, 0))
|
||||
})
|
||||
}
|
@ -18,7 +18,7 @@ type ExportOptions struct {
|
||||
Dest string
|
||||
}
|
||||
|
||||
func newExportCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newExportCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "export [OPTIONS] CONTEXT [FILE|-]",
|
||||
Short: "Export a context to a tar archive FILE or a tar stream on STDOUT.",
|
||||
@ -32,8 +32,9 @@ func newExportCommand(dockerCli command.Cli) *cobra.Command {
|
||||
} else {
|
||||
opts.Dest = opts.ContextName + ".dockercontext"
|
||||
}
|
||||
return RunExport(dockerCli, opts)
|
||||
return RunExport(dockerCLI, opts)
|
||||
},
|
||||
ValidArgsFunction: completeContextNames(dockerCLI, 1, true),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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/context/store"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -19,6 +20,8 @@ func newImportCommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return RunImport(dockerCli, args[0], args[1])
|
||||
},
|
||||
// TODO(thaJeztah): this should also include "-"
|
||||
ValidArgsFunction: completion.FileNames,
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ type inspectOptions struct {
|
||||
}
|
||||
|
||||
// newInspectCommand creates a new cobra.Command for `docker context inspect`
|
||||
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newInspectCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
var opts inspectOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -28,13 +28,14 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.refs = args
|
||||
if len(opts.refs) == 0 {
|
||||
if dockerCli.CurrentContext() == "" {
|
||||
if dockerCLI.CurrentContext() == "" {
|
||||
return errors.New("no context specified")
|
||||
}
|
||||
opts.refs = []string{dockerCli.CurrentContext()}
|
||||
opts.refs = []string{dockerCLI.CurrentContext()}
|
||||
}
|
||||
return runInspect(dockerCli, opts)
|
||||
return runInspect(dockerCLI, opts)
|
||||
},
|
||||
ValidArgsFunction: completeContextNames(dockerCLI, -1, false),
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
@ -16,7 +16,7 @@ type RemoveOptions struct {
|
||||
Force bool
|
||||
}
|
||||
|
||||
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newRemoveCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
var opts RemoveOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm CONTEXT [CONTEXT...]",
|
||||
@ -24,8 +24,9 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
Short: "Remove one or more contexts",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return RunRemove(dockerCli, opts, args)
|
||||
return RunRemove(dockerCLI, opts, args)
|
||||
},
|
||||
ValidArgsFunction: completeContextNames(dockerCLI, -1, false),
|
||||
}
|
||||
cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "Force the removal of a context in use")
|
||||
return cmd
|
||||
|
@ -33,7 +33,7 @@ func longUpdateDescription() string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newUpdateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
opts := &UpdateOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "update [OPTIONS] CONTEXT",
|
||||
@ -41,9 +41,10 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.Name = args[0]
|
||||
return RunUpdate(dockerCli, opts)
|
||||
return RunUpdate(dockerCLI, opts)
|
||||
},
|
||||
Long: longUpdateDescription(),
|
||||
Long: longUpdateDescription(),
|
||||
ValidArgsFunction: completeContextNames(dockerCLI, 1, false),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.Description, "description", "", "Description of the context")
|
||||
|
@ -10,15 +10,16 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newUseCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newUseCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "use CONTEXT",
|
||||
Short: "Set the current docker context",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
return RunUse(dockerCli, name)
|
||||
return RunUse(dockerCLI, name)
|
||||
},
|
||||
ValidArgsFunction: completeContextNames(dockerCLI, 1, false),
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user