Skip to content

Commit 2da5f06

Browse files
authored
Merge pull request #5238 from thaJeztah/completion_improvements
various improvements to shell completions
2 parents 05a8081 + b1c0ddc commit 2da5f06

File tree

13 files changed

+184
-44
lines changed

13 files changed

+184
-44
lines changed

cli/command/completion/functions.go

+36
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package completion
22

33
import (
44
"os"
5+
"strings"
56

67
"github.com/docker/cli/cli/command/formatter"
78
"github.com/docker/docker/api/types/container"
@@ -105,6 +106,41 @@ func NetworkNames(dockerCLI APIClientProvider) ValidArgsFn {
105106
}
106107
}
107108

109+
// EnvVarNames offers completion for environment-variable names. This
110+
// completion can be used for "--env" and "--build-arg" flags, which
111+
// allow obtaining the value of the given environment-variable if present
112+
// in the local environment, so we only should complete the names of the
113+
// environment variables, and not their value. This also prevents the
114+
// completion script from printing values of environment variables
115+
// containing sensitive values.
116+
//
117+
// For example;
118+
//
119+
// export MY_VAR=hello
120+
// docker run --rm --env MY_VAR alpine printenv MY_VAR
121+
// hello
122+
func EnvVarNames(_ *cobra.Command, _ []string, _ string) (names []string, _ cobra.ShellCompDirective) {
123+
envs := os.Environ()
124+
names = make([]string, 0, len(envs))
125+
for _, env := range envs {
126+
name, _, _ := strings.Cut(env, "=")
127+
names = append(names, name)
128+
}
129+
return names, cobra.ShellCompDirectiveNoFileComp
130+
}
131+
132+
// FromList offers completion for the given list of options.
133+
func FromList(options ...string) ValidArgsFn {
134+
return cobra.FixedCompletions(options, cobra.ShellCompDirectiveNoFileComp)
135+
}
136+
137+
// FileNames is a convenience function to use [cobra.ShellCompDirectiveDefault],
138+
// which indicates to let the shell perform its default behavior after
139+
// completions have been provided.
140+
func FileNames(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
141+
return nil, cobra.ShellCompDirectiveDefault
142+
}
143+
108144
// NoComplete is used for commands where there's no relevant completion
109145
func NoComplete(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
110146
return nil, cobra.ShellCompDirectiveNoFileComp

cli/command/container/completion.go

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package container
2+
3+
import (
4+
"github.com/docker/cli/cli/command/completion"
5+
"github.com/docker/docker/api/types/container"
6+
"github.com/moby/sys/signal"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
// allLinuxCapabilities is a list of all known Linux capabilities.
11+
//
12+
// This list was based on the containerd pkg/cap package;
13+
// https://github.com/containerd/containerd/blob/v1.7.19/pkg/cap/cap_linux.go#L133-L181
14+
//
15+
// TODO(thaJeztah): add descriptions, and enable descriptions for our completion scripts (cobra.CompletionOptions.DisableDescriptions is currently set to "true")
16+
var allLinuxCapabilities = []string{
17+
"ALL", // magic value for "all capabilities"
18+
19+
// caps35 is the caps of kernel 3.5 (37 entries)
20+
"CAP_CHOWN", // 2.2
21+
"CAP_DAC_OVERRIDE", // 2.2
22+
"CAP_DAC_READ_SEARCH", // 2.2
23+
"CAP_FOWNER", // 2.2
24+
"CAP_FSETID", // 2.2
25+
"CAP_KILL", // 2.2
26+
"CAP_SETGID", // 2.2
27+
"CAP_SETUID", // 2.2
28+
"CAP_SETPCAP", // 2.2
29+
"CAP_LINUX_IMMUTABLE", // 2.2
30+
"CAP_NET_BIND_SERVICE", // 2.2
31+
"CAP_NET_BROADCAST", // 2.2
32+
"CAP_NET_ADMIN", // 2.2
33+
"CAP_NET_RAW", // 2.2
34+
"CAP_IPC_LOCK", // 2.2
35+
"CAP_IPC_OWNER", // 2.2
36+
"CAP_SYS_MODULE", // 2.2
37+
"CAP_SYS_RAWIO", // 2.2
38+
"CAP_SYS_CHROOT", // 2.2
39+
"CAP_SYS_PTRACE", // 2.2
40+
"CAP_SYS_PACCT", // 2.2
41+
"CAP_SYS_ADMIN", // 2.2
42+
"CAP_SYS_BOOT", // 2.2
43+
"CAP_SYS_NICE", // 2.2
44+
"CAP_SYS_RESOURCE", // 2.2
45+
"CAP_SYS_TIME", // 2.2
46+
"CAP_SYS_TTY_CONFIG", // 2.2
47+
"CAP_MKNOD", // 2.4
48+
"CAP_LEASE", // 2.4
49+
"CAP_AUDIT_WRITE", // 2.6.11
50+
"CAP_AUDIT_CONTROL", // 2.6.11
51+
"CAP_SETFCAP", // 2.6.24
52+
"CAP_MAC_OVERRIDE", // 2.6.25
53+
"CAP_MAC_ADMIN", // 2.6.25
54+
"CAP_SYSLOG", // 2.6.37
55+
"CAP_WAKE_ALARM", // 3.0
56+
"CAP_BLOCK_SUSPEND", // 3.5
57+
58+
// caps316 is the caps of kernel 3.16 (38 entries)
59+
"CAP_AUDIT_READ",
60+
61+
// caps58 is the caps of kernel 5.8 (40 entries)
62+
"CAP_PERFMON",
63+
"CAP_BPF",
64+
65+
// caps59 is the caps of kernel 5.9 (41 entries)
66+
"CAP_CHECKPOINT_RESTORE",
67+
}
68+
69+
// restartPolicies is a list of all valid restart-policies..
70+
//
71+
// TODO(thaJeztah): add descriptions, and enable descriptions for our completion scripts (cobra.CompletionOptions.DisableDescriptions is currently set to "true")
72+
var restartPolicies = []string{
73+
string(container.RestartPolicyDisabled),
74+
string(container.RestartPolicyAlways),
75+
string(container.RestartPolicyOnFailure),
76+
string(container.RestartPolicyUnlessStopped),
77+
}
78+
79+
func completeLinuxCapabilityNames(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) {
80+
return completion.FromList(allLinuxCapabilities...)(cmd, args, toComplete)
81+
}
82+
83+
func completeRestartPolicies(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) {
84+
return completion.FromList(restartPolicies...)(cmd, args, toComplete)
85+
}
86+
87+
func completeSignals(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) {
88+
// TODO(thaJeztah): do we want to provide the full list here, or a subset?
89+
signalNames := make([]string, 0, len(signal.SignalMap))
90+
for k := range signal.SignalMap {
91+
signalNames = append(signalNames, k)
92+
}
93+
return completion.FromList(signalNames...)(cmd, args, toComplete)
94+
}

cli/command/container/create.go

+10
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
7777
command.AddPlatformFlag(flags, &options.platform)
7878
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
7979
copts = addFlags(flags)
80+
81+
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames)
82+
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames)
83+
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
84+
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
85+
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli))
86+
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever))
87+
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
88+
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals)
89+
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCli, true))
8090
return cmd
8191
}
8292

cli/command/container/exec.go

+2-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"io"
7-
"os"
87

98
"github.com/docker/cli/cli"
109
"github.com/docker/cli/cli/command"
@@ -78,12 +77,8 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
7877
flags.StringVarP(&options.Workdir, "workdir", "w", "", "Working directory inside the container")
7978
flags.SetAnnotation("workdir", "version", []string{"1.35"})
8079

81-
_ = cmd.RegisterFlagCompletionFunc("env", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
82-
return os.Environ(), cobra.ShellCompDirectiveNoFileComp
83-
})
84-
_ = cmd.RegisterFlagCompletionFunc("env-file", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
85-
return nil, cobra.ShellCompDirectiveDefault // _filedir
86-
})
80+
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
81+
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
8782

8883
return cmd
8984
}

cli/command/container/kill.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command {
3838

3939
flags := cmd.Flags()
4040
flags.StringVarP(&opts.signal, "signal", "s", "", "Signal to send to the container")
41+
42+
_ = cmd.RegisterFlagCompletionFunc("signal", completeSignals)
43+
4144
return cmd
4245
}
4346

@@ -50,7 +53,7 @@ func runKill(ctx context.Context, dockerCli command.Cli, opts *killOptions) erro
5053
if err := <-errChan; err != nil {
5154
errs = append(errs, err.Error())
5255
} else {
53-
fmt.Fprintln(dockerCli.Out(), name)
56+
_, _ = fmt.Fprintln(dockerCli.Out(), name)
5457
}
5558
}
5659
if len(errs) > 0 {

cli/command/container/restart.go

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
4343
flags := cmd.Flags()
4444
flags.StringVarP(&opts.signal, "signal", "s", "", "Signal to send to the container")
4545
flags.IntVarP(&opts.timeout, "time", "t", 0, "Seconds to wait before killing the container")
46+
47+
_ = cmd.RegisterFlagCompletionFunc("signal", completeSignals)
48+
4649
return cmd
4750
}
4851

cli/command/container/run.go

+9-17
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"io"
7-
"os"
87
"strings"
98
"syscall"
109

@@ -70,22 +69,15 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
7069
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
7170
copts = addFlags(flags)
7271

73-
cmd.RegisterFlagCompletionFunc(
74-
"env",
75-
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
76-
return os.Environ(), cobra.ShellCompDirectiveNoFileComp
77-
},
78-
)
79-
cmd.RegisterFlagCompletionFunc(
80-
"env-file",
81-
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
82-
return nil, cobra.ShellCompDirectiveDefault
83-
},
84-
)
85-
cmd.RegisterFlagCompletionFunc(
86-
"network",
87-
completion.NetworkNames(dockerCli),
88-
)
72+
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames)
73+
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames)
74+
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
75+
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
76+
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli))
77+
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever))
78+
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
79+
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals)
80+
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCli, true))
8981
return cmd
9082
}
9183

cli/command/container/stop.go

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ func NewStopCommand(dockerCli command.Cli) *cobra.Command {
4343
flags := cmd.Flags()
4444
flags.StringVarP(&opts.signal, "signal", "s", "", "Signal to send to the container")
4545
flags.IntVarP(&opts.timeout, "time", "t", 0, "Seconds to wait before killing the container")
46+
47+
_ = cmd.RegisterFlagCompletionFunc("signal", completeSignals)
48+
4649
return cmd
4750
}
4851

cli/command/container/update.go

+2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ func NewUpdateCommand(dockerCli command.Cli) *cobra.Command {
8383
flags.Var(&options.cpus, "cpus", "Number of CPUs")
8484
flags.SetAnnotation("cpus", "version", []string{"1.29"})
8585

86+
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
87+
8688
return cmd
8789
}
8890

cli/context/store/store.go

+3
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ func (s *ContextStore) List() ([]Metadata, error) {
124124

125125
// Names return Metadata names for a Lister
126126
func Names(s Lister) ([]string, error) {
127+
if s == nil {
128+
return nil, errors.New("nil lister")
129+
}
127130
list, err := s.List()
128131
if err != nil {
129132
return nil, err

cli/context/store/store_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,9 @@ func TestCorruptMetadata(t *testing.T) {
260260
_, err = s.GetMetadata("source")
261261
assert.ErrorContains(t, err, fmt.Sprintf("parsing %s: unexpected end of JSON input", contextFile))
262262
}
263+
264+
func TestNames(t *testing.T) {
265+
names, err := Names(nil)
266+
assert.Check(t, is.Error(err, "nil lister"))
267+
assert.Check(t, is.Len(names, 0))
268+
}

cmd/docker/completions.go

+11-18
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,24 @@
11
package main
22

33
import (
4+
"github.com/docker/cli/cli/command/completion"
45
"github.com/docker/cli/cli/context/store"
56
"github.com/spf13/cobra"
67
)
78

8-
func registerCompletionFuncForGlobalFlags(contextStore store.Store, cmd *cobra.Command) error {
9-
err := cmd.RegisterFlagCompletionFunc(
10-
"context",
11-
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
12-
names, err := store.Names(contextStore)
13-
if err != nil {
14-
return nil, cobra.ShellCompDirectiveError
15-
}
16-
return names, cobra.ShellCompDirectiveNoFileComp
17-
},
18-
)
9+
type contextStoreProvider interface {
10+
ContextStore() store.Store
11+
}
12+
13+
func registerCompletionFuncForGlobalFlags(dockerCLI contextStoreProvider, cmd *cobra.Command) error {
14+
err := cmd.RegisterFlagCompletionFunc("context", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
15+
names, _ := store.Names(dockerCLI.ContextStore())
16+
return names, cobra.ShellCompDirectiveNoFileComp
17+
})
1918
if err != nil {
2019
return err
2120
}
22-
err = cmd.RegisterFlagCompletionFunc(
23-
"log-level",
24-
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
25-
values := []string{"debug", "info", "warn", "error", "fatal"}
26-
return values, cobra.ShellCompDirectiveNoFileComp
27-
},
28-
)
21+
err = cmd.RegisterFlagCompletionFunc("log-level", completion.FromList("debug", "info", "warn", "error", "fatal"))
2922
if err != nil {
3023
return err
3124
}

cmd/docker/docker.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func newDockerCommand(dockerCli *command.DockerCli) *cli.TopLevelCommand {
100100
cmd.SetErr(dockerCli.Err())
101101

102102
opts, helpCmd = cli.SetupRootCommand(cmd)
103-
_ = registerCompletionFuncForGlobalFlags(dockerCli.ContextStore(), cmd)
103+
_ = registerCompletionFuncForGlobalFlags(dockerCli, cmd)
104104
cmd.Flags().BoolP("version", "v", false, "Print version information and quit")
105105
setFlagErrorFunc(dockerCli, cmd)
106106

0 commit comments

Comments
 (0)