mirror of https://github.com/docker/cli.git
Merge pull request #5238 from thaJeztah/completion_improvements
various improvements to shell completions
This commit is contained in:
commit
2da5f06962
|
@ -2,6 +2,7 @@ package completion
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
|
@ -105,6 +106,41 @@ func NetworkNames(dockerCLI APIClientProvider) ValidArgsFn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnvVarNames offers completion for environment-variable names. This
|
||||||
|
// completion can be used for "--env" and "--build-arg" flags, which
|
||||||
|
// allow obtaining the value of the given environment-variable if present
|
||||||
|
// in the local environment, so we only should complete the names of the
|
||||||
|
// environment variables, and not their value. This also prevents the
|
||||||
|
// completion script from printing values of environment variables
|
||||||
|
// containing sensitive values.
|
||||||
|
//
|
||||||
|
// For example;
|
||||||
|
//
|
||||||
|
// export MY_VAR=hello
|
||||||
|
// docker run --rm --env MY_VAR alpine printenv MY_VAR
|
||||||
|
// hello
|
||||||
|
func EnvVarNames(_ *cobra.Command, _ []string, _ string) (names []string, _ cobra.ShellCompDirective) {
|
||||||
|
envs := os.Environ()
|
||||||
|
names = make([]string, 0, len(envs))
|
||||||
|
for _, env := range envs {
|
||||||
|
name, _, _ := strings.Cut(env, "=")
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromList offers completion for the given list of options.
|
||||||
|
func FromList(options ...string) ValidArgsFn {
|
||||||
|
return cobra.FixedCompletions(options, cobra.ShellCompDirectiveNoFileComp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileNames is a convenience function to use [cobra.ShellCompDirectiveDefault],
|
||||||
|
// which indicates to let the shell perform its default behavior after
|
||||||
|
// completions have been provided.
|
||||||
|
func FileNames(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return nil, cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
// NoComplete is used for commands where there's no relevant completion
|
// NoComplete is used for commands where there's no relevant completion
|
||||||
func NoComplete(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
func NoComplete(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/cli/cli/command/completion"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/moby/sys/signal"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// allLinuxCapabilities is a list of all known Linux capabilities.
|
||||||
|
//
|
||||||
|
// This list was based on the containerd pkg/cap package;
|
||||||
|
// https://github.com/containerd/containerd/blob/v1.7.19/pkg/cap/cap_linux.go#L133-L181
|
||||||
|
//
|
||||||
|
// TODO(thaJeztah): add descriptions, and enable descriptions for our completion scripts (cobra.CompletionOptions.DisableDescriptions is currently set to "true")
|
||||||
|
var allLinuxCapabilities = []string{
|
||||||
|
"ALL", // magic value for "all capabilities"
|
||||||
|
|
||||||
|
// caps35 is the caps of kernel 3.5 (37 entries)
|
||||||
|
"CAP_CHOWN", // 2.2
|
||||||
|
"CAP_DAC_OVERRIDE", // 2.2
|
||||||
|
"CAP_DAC_READ_SEARCH", // 2.2
|
||||||
|
"CAP_FOWNER", // 2.2
|
||||||
|
"CAP_FSETID", // 2.2
|
||||||
|
"CAP_KILL", // 2.2
|
||||||
|
"CAP_SETGID", // 2.2
|
||||||
|
"CAP_SETUID", // 2.2
|
||||||
|
"CAP_SETPCAP", // 2.2
|
||||||
|
"CAP_LINUX_IMMUTABLE", // 2.2
|
||||||
|
"CAP_NET_BIND_SERVICE", // 2.2
|
||||||
|
"CAP_NET_BROADCAST", // 2.2
|
||||||
|
"CAP_NET_ADMIN", // 2.2
|
||||||
|
"CAP_NET_RAW", // 2.2
|
||||||
|
"CAP_IPC_LOCK", // 2.2
|
||||||
|
"CAP_IPC_OWNER", // 2.2
|
||||||
|
"CAP_SYS_MODULE", // 2.2
|
||||||
|
"CAP_SYS_RAWIO", // 2.2
|
||||||
|
"CAP_SYS_CHROOT", // 2.2
|
||||||
|
"CAP_SYS_PTRACE", // 2.2
|
||||||
|
"CAP_SYS_PACCT", // 2.2
|
||||||
|
"CAP_SYS_ADMIN", // 2.2
|
||||||
|
"CAP_SYS_BOOT", // 2.2
|
||||||
|
"CAP_SYS_NICE", // 2.2
|
||||||
|
"CAP_SYS_RESOURCE", // 2.2
|
||||||
|
"CAP_SYS_TIME", // 2.2
|
||||||
|
"CAP_SYS_TTY_CONFIG", // 2.2
|
||||||
|
"CAP_MKNOD", // 2.4
|
||||||
|
"CAP_LEASE", // 2.4
|
||||||
|
"CAP_AUDIT_WRITE", // 2.6.11
|
||||||
|
"CAP_AUDIT_CONTROL", // 2.6.11
|
||||||
|
"CAP_SETFCAP", // 2.6.24
|
||||||
|
"CAP_MAC_OVERRIDE", // 2.6.25
|
||||||
|
"CAP_MAC_ADMIN", // 2.6.25
|
||||||
|
"CAP_SYSLOG", // 2.6.37
|
||||||
|
"CAP_WAKE_ALARM", // 3.0
|
||||||
|
"CAP_BLOCK_SUSPEND", // 3.5
|
||||||
|
|
||||||
|
// caps316 is the caps of kernel 3.16 (38 entries)
|
||||||
|
"CAP_AUDIT_READ",
|
||||||
|
|
||||||
|
// caps58 is the caps of kernel 5.8 (40 entries)
|
||||||
|
"CAP_PERFMON",
|
||||||
|
"CAP_BPF",
|
||||||
|
|
||||||
|
// caps59 is the caps of kernel 5.9 (41 entries)
|
||||||
|
"CAP_CHECKPOINT_RESTORE",
|
||||||
|
}
|
||||||
|
|
||||||
|
// restartPolicies is a list of all valid restart-policies..
|
||||||
|
//
|
||||||
|
// TODO(thaJeztah): add descriptions, and enable descriptions for our completion scripts (cobra.CompletionOptions.DisableDescriptions is currently set to "true")
|
||||||
|
var restartPolicies = []string{
|
||||||
|
string(container.RestartPolicyDisabled),
|
||||||
|
string(container.RestartPolicyAlways),
|
||||||
|
string(container.RestartPolicyOnFailure),
|
||||||
|
string(container.RestartPolicyUnlessStopped),
|
||||||
|
}
|
||||||
|
|
||||||
|
func completeLinuxCapabilityNames(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) {
|
||||||
|
return completion.FromList(allLinuxCapabilities...)(cmd, args, toComplete)
|
||||||
|
}
|
||||||
|
|
||||||
|
func completeRestartPolicies(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) {
|
||||||
|
return completion.FromList(restartPolicies...)(cmd, args, toComplete)
|
||||||
|
}
|
||||||
|
|
||||||
|
func completeSignals(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) {
|
||||||
|
// TODO(thaJeztah): do we want to provide the full list here, or a subset?
|
||||||
|
signalNames := make([]string, 0, len(signal.SignalMap))
|
||||||
|
for k := range signal.SignalMap {
|
||||||
|
signalNames = append(signalNames, k)
|
||||||
|
}
|
||||||
|
return completion.FromList(signalNames...)(cmd, args, toComplete)
|
||||||
|
}
|
|
@ -77,6 +77,16 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
command.AddPlatformFlag(flags, &options.platform)
|
command.AddPlatformFlag(flags, &options.platform)
|
||||||
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
||||||
copts = addFlags(flags)
|
copts = addFlags(flags)
|
||||||
|
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli))
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever))
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCli, true))
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
@ -78,12 +77,8 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
flags.StringVarP(&options.Workdir, "workdir", "w", "", "Working directory inside the container")
|
flags.StringVarP(&options.Workdir, "workdir", "w", "", "Working directory inside the container")
|
||||||
flags.SetAnnotation("workdir", "version", []string{"1.35"})
|
flags.SetAnnotation("workdir", "version", []string{"1.35"})
|
||||||
|
|
||||||
_ = cmd.RegisterFlagCompletionFunc("env", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
|
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
|
||||||
return os.Environ(), cobra.ShellCompDirectiveNoFileComp
|
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
|
||||||
})
|
|
||||||
_ = cmd.RegisterFlagCompletionFunc("env-file", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
|
|
||||||
return nil, cobra.ShellCompDirectiveDefault // _filedir
|
|
||||||
})
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,9 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVarP(&opts.signal, "signal", "s", "", "Signal to send to the container")
|
flags.StringVarP(&opts.signal, "signal", "s", "", "Signal to send to the container")
|
||||||
|
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("signal", completeSignals)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +53,7 @@ func runKill(ctx context.Context, dockerCli command.Cli, opts *killOptions) erro
|
||||||
if err := <-errChan; err != nil {
|
if err := <-errChan; err != nil {
|
||||||
errs = append(errs, err.Error())
|
errs = append(errs, err.Error())
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintln(dockerCli.Out(), name)
|
_, _ = fmt.Fprintln(dockerCli.Out(), name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
|
|
|
@ -43,6 +43,9 @@ func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVarP(&opts.signal, "signal", "s", "", "Signal to send to the container")
|
flags.StringVarP(&opts.signal, "signal", "s", "", "Signal to send to the container")
|
||||||
flags.IntVarP(&opts.timeout, "time", "t", 0, "Seconds to wait before killing the container")
|
flags.IntVarP(&opts.timeout, "time", "t", 0, "Seconds to wait before killing the container")
|
||||||
|
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("signal", completeSignals)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
@ -70,22 +69,15 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
||||||
copts = addFlags(flags)
|
copts = addFlags(flags)
|
||||||
|
|
||||||
cmd.RegisterFlagCompletionFunc(
|
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames)
|
||||||
"env",
|
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames)
|
||||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
|
||||||
return os.Environ(), cobra.ShellCompDirectiveNoFileComp
|
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
|
||||||
},
|
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli))
|
||||||
)
|
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever))
|
||||||
cmd.RegisterFlagCompletionFunc(
|
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
|
||||||
"env-file",
|
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals)
|
||||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCli, true))
|
||||||
return nil, cobra.ShellCompDirectiveDefault
|
|
||||||
},
|
|
||||||
)
|
|
||||||
cmd.RegisterFlagCompletionFunc(
|
|
||||||
"network",
|
|
||||||
completion.NetworkNames(dockerCli),
|
|
||||||
)
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,9 @@ func NewStopCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVarP(&opts.signal, "signal", "s", "", "Signal to send to the container")
|
flags.StringVarP(&opts.signal, "signal", "s", "", "Signal to send to the container")
|
||||||
flags.IntVarP(&opts.timeout, "time", "t", 0, "Seconds to wait before killing the container")
|
flags.IntVarP(&opts.timeout, "time", "t", 0, "Seconds to wait before killing the container")
|
||||||
|
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("signal", completeSignals)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,8 @@ func NewUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
flags.Var(&options.cpus, "cpus", "Number of CPUs")
|
flags.Var(&options.cpus, "cpus", "Number of CPUs")
|
||||||
flags.SetAnnotation("cpus", "version", []string{"1.29"})
|
flags.SetAnnotation("cpus", "version", []string{"1.29"})
|
||||||
|
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,9 @@ func (s *ContextStore) List() ([]Metadata, error) {
|
||||||
|
|
||||||
// Names return Metadata names for a Lister
|
// Names return Metadata names for a Lister
|
||||||
func Names(s Lister) ([]string, error) {
|
func Names(s Lister) ([]string, error) {
|
||||||
|
if s == nil {
|
||||||
|
return nil, errors.New("nil lister")
|
||||||
|
}
|
||||||
list, err := s.List()
|
list, err := s.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -260,3 +260,9 @@ func TestCorruptMetadata(t *testing.T) {
|
||||||
_, err = s.GetMetadata("source")
|
_, err = s.GetMetadata("source")
|
||||||
assert.ErrorContains(t, err, fmt.Sprintf("parsing %s: unexpected end of JSON input", contextFile))
|
assert.ErrorContains(t, err, fmt.Sprintf("parsing %s: unexpected end of JSON input", contextFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNames(t *testing.T) {
|
||||||
|
names, err := Names(nil)
|
||||||
|
assert.Check(t, is.Error(err, "nil lister"))
|
||||||
|
assert.Check(t, is.Len(names, 0))
|
||||||
|
}
|
||||||
|
|
|
@ -1,31 +1,24 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerCompletionFuncForGlobalFlags(contextStore store.Store, cmd *cobra.Command) error {
|
type contextStoreProvider interface {
|
||||||
err := cmd.RegisterFlagCompletionFunc(
|
ContextStore() store.Store
|
||||||
"context",
|
}
|
||||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
||||||
names, err := store.Names(contextStore)
|
func registerCompletionFuncForGlobalFlags(dockerCLI contextStoreProvider, cmd *cobra.Command) error {
|
||||||
if err != nil {
|
err := cmd.RegisterFlagCompletionFunc("context", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
|
||||||
return nil, cobra.ShellCompDirectiveError
|
names, _ := store.Names(dockerCLI.ContextStore())
|
||||||
}
|
return names, cobra.ShellCompDirectiveNoFileComp
|
||||||
return names, cobra.ShellCompDirectiveNoFileComp
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = cmd.RegisterFlagCompletionFunc(
|
err = cmd.RegisterFlagCompletionFunc("log-level", completion.FromList("debug", "info", "warn", "error", "fatal"))
|
||||||
"log-level",
|
|
||||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
||||||
values := []string{"debug", "info", "warn", "error", "fatal"}
|
|
||||||
return values, cobra.ShellCompDirectiveNoFileComp
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ func newDockerCommand(dockerCli *command.DockerCli) *cli.TopLevelCommand {
|
||||||
cmd.SetErr(dockerCli.Err())
|
cmd.SetErr(dockerCli.Err())
|
||||||
|
|
||||||
opts, helpCmd = cli.SetupRootCommand(cmd)
|
opts, helpCmd = cli.SetupRootCommand(cmd)
|
||||||
_ = registerCompletionFuncForGlobalFlags(dockerCli.ContextStore(), cmd)
|
_ = registerCompletionFuncForGlobalFlags(dockerCli, cmd)
|
||||||
cmd.Flags().BoolP("version", "v", false, "Print version information and quit")
|
cmd.Flags().BoolP("version", "v", false, "Print version information and quit")
|
||||||
setFlagErrorFunc(dockerCli, cmd)
|
setFlagErrorFunc(dockerCli, cmd)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue