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
|
Dest string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newExportCommand(dockerCli command.Cli) *cobra.Command {
|
func newExportCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "export [OPTIONS] CONTEXT [FILE|-]",
|
Use: "export [OPTIONS] CONTEXT [FILE|-]",
|
||||||
Short: "Export a context to a tar archive FILE or a tar stream on STDOUT.",
|
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 {
|
} else {
|
||||||
opts.Dest = opts.ContextName + ".dockercontext"
|
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"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/command/completion"
|
||||||
"github.com/docker/cli/cli/context/store"
|
"github.com/docker/cli/cli/context/store"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@ -19,6 +20,8 @@ func newImportCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return RunImport(dockerCli, args[0], args[1])
|
return RunImport(dockerCli, args[0], args[1])
|
||||||
},
|
},
|
||||||
|
// TODO(thaJeztah): this should also include "-"
|
||||||
|
ValidArgsFunction: completion.FileNames,
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ type inspectOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newInspectCommand creates a new cobra.Command for `docker context inspect`
|
// 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
|
var opts inspectOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@ -28,13 +28,14 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.refs = args
|
opts.refs = args
|
||||||
if len(opts.refs) == 0 {
|
if len(opts.refs) == 0 {
|
||||||
if dockerCli.CurrentContext() == "" {
|
if dockerCLI.CurrentContext() == "" {
|
||||||
return errors.New("no context specified")
|
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()
|
flags := cmd.Flags()
|
||||||
|
@ -16,7 +16,7 @@ type RemoveOptions struct {
|
|||||||
Force bool
|
Force bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
func newRemoveCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
var opts RemoveOptions
|
var opts RemoveOptions
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "rm CONTEXT [CONTEXT...]",
|
Use: "rm CONTEXT [CONTEXT...]",
|
||||||
@ -24,8 +24,9 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
Short: "Remove one or more contexts",
|
Short: "Remove one or more contexts",
|
||||||
Args: cli.RequiresMinArgs(1),
|
Args: cli.RequiresMinArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
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")
|
cmd.Flags().BoolVarP(&opts.Force, "force", "f", false, "Force the removal of a context in use")
|
||||||
return cmd
|
return cmd
|
||||||
|
@ -33,7 +33,7 @@ func longUpdateDescription() string {
|
|||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
func newUpdateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
opts := &UpdateOptions{}
|
opts := &UpdateOptions{}
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "update [OPTIONS] CONTEXT",
|
Use: "update [OPTIONS] CONTEXT",
|
||||||
@ -41,9 +41,10 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.Name = args[0]
|
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 := cmd.Flags()
|
||||||
flags.StringVar(&opts.Description, "description", "", "Description of the context")
|
flags.StringVar(&opts.Description, "description", "", "Description of the context")
|
||||||
|
@ -10,15 +10,16 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newUseCommand(dockerCli command.Cli) *cobra.Command {
|
func newUseCommand(dockerCLI command.Cli) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "use CONTEXT",
|
Use: "use CONTEXT",
|
||||||
Short: "Set the current docker context",
|
Short: "Set the current docker context",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
name := args[0]
|
name := args[0]
|
||||||
return RunUse(dockerCli, name)
|
return RunUse(dockerCLI, name)
|
||||||
},
|
},
|
||||||
|
ValidArgsFunction: completeContextNames(dockerCLI, 1, false),
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user