-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve Cobra completions for run
and create
#5580
base: master
Are you sure you want to change the base?
Changes from all commits
cc981e3
d9766a3
98d5b03
55d4192
75d8077
fe1db01
00e3b7c
4450f48
285d0e3
14038a9
85ab121
9906847
6aba94e
a5aa7c9
d963cfc
3ab5af7
e08a007
784ad51
b9c7f6c
77fd7d7
33d870a
846439c
0e4b312
93af729
6a801bc
e27ff3b
0135d7e
31f1f43
b3703fa
39cd60b
9817e32
5c96388
968d758
39028ce
a40ed99
ff9c684
396d9e0
09701fe
a490538
0d65aca
fd1d55a
20538ac
b878f5e
46db741
8afd8dd
61e803c
fc12d09
eca980c
bdae758
32d7ff8
afda9f3
5624d2c
8362a23
adcce2b
a40ea78
3bc177b
33d3246
ef6cca9
4378ab4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -44,6 +44,65 @@ var allLinuxCapabilities = sync.OnceValue(func() []string { | |||||
return out | ||||||
}) | ||||||
|
||||||
// logDriverOptions provides the options for each built-in logging driver. | ||||||
var logDriverOptions = map[string][]string{ | ||||||
"awslogs": { | ||||||
"max-buffer-size", "mode", "awslogs-create-group", "awslogs-credentials-endpoint", "awslogs-datetime-format", | ||||||
"awslogs-group", "awslogs-multiline-pattern", "awslogs-region", "awslogs-stream", "tag", | ||||||
}, | ||||||
"fluentd": { | ||||||
"max-buffer-size", "mode", "env", "env-regex", "labels", "fluentd-address", "fluentd-async", | ||||||
"fluentd-buffer-limit", "fluentd-request-ack", "fluentd-retry-wait", "fluentd-max-retries", | ||||||
"fluentd-sub-second-precision", "tag", | ||||||
}, | ||||||
"gcplogs": { | ||||||
"max-buffer-size", "mode", "env", "env-regex", "labels", "gcp-log-cmd", "gcp-meta-id", "gcp-meta-name", | ||||||
"gcp-meta-zone", "gcp-project", | ||||||
}, | ||||||
"gelf": { | ||||||
"max-buffer-size", "mode", "env", "env-regex", "labels", "gelf-address", "gelf-compression-level", | ||||||
"gelf-compression-type", "gelf-tcp-max-reconnect", "gelf-tcp-reconnect-delay", "tag", | ||||||
}, | ||||||
"journald": {"max-buffer-size", "mode", "env", "env-regex", "labels", "tag"}, | ||||||
"json-file": {"max-buffer-size", "mode", "env", "env-regex", "labels", "compress", "max-file", "max-size"}, | ||||||
"local": {"max-buffer-size", "mode", "compress", "max-file", "max-size"}, | ||||||
"none": {}, | ||||||
"splunk": { | ||||||
"max-buffer-size", "mode", "env", "env-regex", "labels", "splunk-caname", "splunk-capath", "splunk-format", | ||||||
"splunk-gzip", "splunk-gzip-level", "splunk-index", "splunk-insecureskipverify", "splunk-source", | ||||||
"splunk-sourcetype", "splunk-token", "splunk-url", "splunk-verify-connection", "tag", | ||||||
}, | ||||||
"syslog": { | ||||||
"max-buffer-size", "mode", "env", "env-regex", "labels", "syslog-address", "syslog-facility", "syslog-format", | ||||||
"syslog-tls-ca-cert", "syslog-tls-cert", "syslog-tls-key", "syslog-tls-skip-verify", "tag", | ||||||
}, | ||||||
} | ||||||
|
||||||
// builtInLogDrivers provides a list of the built-in logging drivers. | ||||||
var builtInLogDrivers = sync.OnceValue(func() []string { | ||||||
drivers := make([]string, 0, len(logDriverOptions)) | ||||||
for driver := range logDriverOptions { | ||||||
drivers = append(drivers, driver) | ||||||
} | ||||||
return drivers | ||||||
}) | ||||||
|
||||||
// allLogDriverOptions provides all options of the built-in logging drivers. | ||||||
// The list does not contain duplicates. | ||||||
var allLogDriverOptions = sync.OnceValue(func() []string { | ||||||
var result []string | ||||||
seen := make(map[string]bool) | ||||||
for driver := range logDriverOptions { | ||||||
for _, opt := range logDriverOptions[driver] { | ||||||
if !seen[opt] { | ||||||
seen[opt] = true | ||||||
result = append(result, opt) | ||||||
} | ||||||
} | ||||||
} | ||||||
return result | ||||||
}) | ||||||
|
||||||
// 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") | ||||||
|
@@ -54,6 +113,206 @@ var restartPolicies = []string{ | |||||
string(container.RestartPolicyUnlessStopped), | ||||||
} | ||||||
|
||||||
// addCompletions adds the completions that `run` and `create` have in common. | ||||||
func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) { | ||||||
_ = cmd.RegisterFlagCompletionFunc("attach", completion.FromList("stderr", "stdin", "stdout")) | ||||||
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames) | ||||||
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames) | ||||||
_ = cmd.RegisterFlagCompletionFunc("cgroupns", completeCgroupns()) | ||||||
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames) | ||||||
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames) | ||||||
_ = cmd.RegisterFlagCompletionFunc("ipc", completeIpc(dockerCLI)) | ||||||
_ = cmd.RegisterFlagCompletionFunc("link", completeLink(dockerCLI)) | ||||||
_ = cmd.RegisterFlagCompletionFunc("log-driver", completeLogDriver(dockerCLI)) | ||||||
_ = cmd.RegisterFlagCompletionFunc("log-opt", completeLogOpt) | ||||||
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCLI)) | ||||||
_ = cmd.RegisterFlagCompletionFunc("pid", completePid(dockerCLI)) | ||||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) | ||||||
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever)) | ||||||
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies) | ||||||
_ = cmd.RegisterFlagCompletionFunc("security-opt", completeSecurityOpt) | ||||||
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals) | ||||||
_ = cmd.RegisterFlagCompletionFunc("storage-opt", completeStorageOpt) | ||||||
_ = cmd.RegisterFlagCompletionFunc("ulimit", completeUlimit) | ||||||
_ = cmd.RegisterFlagCompletionFunc("userns", completion.FromList("host")) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note to self; looks like we don't define an enum/consts for this (there's only a single option, so maybe not really needed, but we do define a type for this; cli/vendor/github.com/docker/docker/api/types/container/hostconfig.go Lines 171 to 172 in 32ff200
|
||||||
_ = cmd.RegisterFlagCompletionFunc("uts", completion.FromList("host")) | ||||||
_ = cmd.RegisterFlagCompletionFunc("volume-driver", completeVolumeDriver(dockerCLI)) | ||||||
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCLI, true)) | ||||||
} | ||||||
|
||||||
func completeCgroupns() completion.ValidArgsFn { | ||||||
return completion.FromList(string(container.CgroupnsModeHost), string(container.CgroupnsModePrivate)) | ||||||
} | ||||||
|
||||||
// completeDetachKeys implements shell completion for the `--detach-keys` option of `run` and `create`. | ||||||
func completeDetachKeys(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { | ||||||
return []string{"ctrl-"}, cobra.ShellCompDirectiveNoSpace | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note to self; I need to lookup if there's other special ones we define ( |
||||||
} | ||||||
|
||||||
// completeIpc implements shell completion for the `--ipc` option of `run` and `create`. | ||||||
// The completion is partly composite. | ||||||
func completeIpc(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||||||
if len(toComplete) > 0 && strings.HasPrefix("container", toComplete) { //nolint:gocritic // not swapped, matches partly typed "container" | ||||||
return []string{"container:"}, cobra.ShellCompDirectiveNoSpace | ||||||
} | ||||||
if strings.HasPrefix(toComplete, "container:") { | ||||||
names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete) | ||||||
return prefixWith("container:", names), cobra.ShellCompDirectiveNoFileComp | ||||||
} | ||||||
return []string{ | ||||||
string(container.IPCModeContainer + ":"), | ||||||
string(container.IPCModeHost), | ||||||
string(container.IPCModeNone), | ||||||
string(container.IPCModePrivate), | ||||||
string(container.IPCModeShareable), | ||||||
}, cobra.ShellCompDirectiveNoFileComp | ||||||
} | ||||||
} | ||||||
|
||||||
// completeLink implements shell completion for the `--link` option of `run` and `create`. | ||||||
func completeLink(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||||||
return postfixWith(":", containerNames(dockerCLI, cmd, args, toComplete)), cobra.ShellCompDirectiveNoSpace | ||||||
} | ||||||
} | ||||||
|
||||||
// completeLogDriver implements shell completion for the `--log-driver` option of `run` and `create`. | ||||||
// The log drivers are collected from a call to the Info endpoint with a fallback to a hard-coded list | ||||||
// of the build-in log drivers. | ||||||
func completeLogDriver(dockerCLI completion.APIClientProvider) completion.ValidArgsFn { | ||||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||||||
info, err := dockerCLI.Client().Info(cmd.Context()) | ||||||
if err != nil { | ||||||
return builtInLogDrivers(), cobra.ShellCompDirectiveNoFileComp | ||||||
} | ||||||
drivers := info.Plugins.Log | ||||||
return drivers, cobra.ShellCompDirectiveNoFileComp | ||||||
} | ||||||
} | ||||||
|
||||||
// completeLogOpt implements shell completion for the `--log-opt` option of `run` and `create`. | ||||||
// If the user supplied a log-driver, only options for that driver are returned. | ||||||
func completeLogOpt(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { | ||||||
driver, _ := cmd.Flags().GetString("log-driver") | ||||||
if options, exists := logDriverOptions[driver]; exists { | ||||||
return postfixWith("=", options), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp | ||||||
} | ||||||
return postfixWith("=", allLogDriverOptions()), cobra.ShellCompDirectiveNoSpace | ||||||
} | ||||||
|
||||||
// completePid implements shell completion for the `--pid` option of `run` and `create`. | ||||||
func completePid(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||||||
if len(toComplete) > 0 && strings.HasPrefix("container", toComplete) { //nolint:gocritic // not swapped, matches partly typed "container" | ||||||
return []string{"container:"}, cobra.ShellCompDirectiveNoSpace | ||||||
} | ||||||
if strings.HasPrefix(toComplete, "container:") { | ||||||
names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete) | ||||||
return prefixWith("container:", names), cobra.ShellCompDirectiveNoFileComp | ||||||
} | ||||||
return []string{"container:", "host"}, cobra.ShellCompDirectiveNoFileComp | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note to self; looks like (unlike IPCMode) we don't define consts for this yet. |
||||||
} | ||||||
} | ||||||
|
||||||
// completeSecurityOpt implements shell completion for the `--security-opt` option of `run` and `create`. | ||||||
// The completion is partly composite. | ||||||
func completeSecurityOpt(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||||||
if len(toComplete) > 0 && strings.HasPrefix("apparmor=", toComplete) { //nolint:gocritic // not swapped, matches partly typed "apparmor=" | ||||||
return []string{"apparmor="}, cobra.ShellCompDirectiveNoSpace | ||||||
} | ||||||
if len(toComplete) > 0 && strings.HasPrefix("label", toComplete) { //nolint:gocritic // not swapped, matches partly typed "label" | ||||||
return []string{"label="}, cobra.ShellCompDirectiveNoSpace | ||||||
} | ||||||
if strings.HasPrefix(toComplete, "label=") { | ||||||
if strings.HasPrefix(toComplete, "label=d") { | ||||||
return []string{"label=disable"}, cobra.ShellCompDirectiveNoFileComp | ||||||
} | ||||||
labels := []string{"disable", "level:", "role:", "type:", "user:"} | ||||||
return prefixWith("label=", labels), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp | ||||||
} | ||||||
// length must be > 1 here so that completion of "s" falls through. | ||||||
if len(toComplete) > 1 && strings.HasPrefix("seccomp", toComplete) { //nolint:gocritic // not swapped, matches partly typed "seccomp" | ||||||
return []string{"seccomp="}, cobra.ShellCompDirectiveNoSpace | ||||||
} | ||||||
if strings.HasPrefix(toComplete, "seccomp=") { | ||||||
return []string{"seccomp=unconfined"}, cobra.ShellCompDirectiveNoFileComp | ||||||
} | ||||||
return []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"}, cobra.ShellCompDirectiveNoFileComp | ||||||
} | ||||||
|
||||||
// completeStorageOpt implements shell completion for the `--storage-opt` option of `run` and `create`. | ||||||
func completeStorageOpt(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { | ||||||
return []string{"size="}, cobra.ShellCompDirectiveNoSpace | ||||||
} | ||||||
|
||||||
// completeUlimit implements shell completion for the `--ulimit` option of `run` and `create`. | ||||||
func completeUlimit(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { | ||||||
limits := []string{ | ||||||
"as", | ||||||
"chroot", | ||||||
"core", | ||||||
"cpu", | ||||||
"data", | ||||||
"fsize", | ||||||
"locks", | ||||||
"maxlogins", | ||||||
"maxsyslogins", | ||||||
"memlock", | ||||||
"msgqueue", | ||||||
"nice", | ||||||
"nofile", | ||||||
"nproc", | ||||||
"priority", | ||||||
"rss", | ||||||
"rtprio", | ||||||
"sigpending", | ||||||
"stack", | ||||||
} | ||||||
return postfixWith("=", limits), cobra.ShellCompDirectiveNoSpace | ||||||
} | ||||||
|
||||||
// completeVolumeDriver contacts the API to get the built-in and installed volume drivers. | ||||||
func completeVolumeDriver(dockerCLI completion.APIClientProvider) completion.ValidArgsFn { | ||||||
return func(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { | ||||||
info, err := dockerCLI.Client().Info(cmd.Context()) | ||||||
if err != nil { | ||||||
// fallback: the built-in drivers | ||||||
return []string{"local"}, cobra.ShellCompDirectiveNoFileComp | ||||||
} | ||||||
drivers := info.Plugins.Volume | ||||||
return drivers, cobra.ShellCompDirectiveNoFileComp | ||||||
} | ||||||
} | ||||||
|
||||||
// containerNames contacts the API to get names and optionally IDs of containers. | ||||||
// In case of an error, an empty list is returned. | ||||||
func containerNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command, args []string, toComplete string) []string { | ||||||
names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete) | ||||||
if names == nil { | ||||||
return []string{} | ||||||
} | ||||||
Comment on lines
+291
to
+294
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious; is this a bug in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I just wanted to get a list of container names that I can feed into From my point of view, a better solution would have been to pull out a function from |
||||||
return names | ||||||
} | ||||||
|
||||||
// prefixWith prefixes every element in the slice with the given prefix. | ||||||
func prefixWith(prefix string, values []string) []string { | ||||||
result := make([]string, len(values)) | ||||||
for i, v := range values { | ||||||
result[i] = prefix + v | ||||||
} | ||||||
return result | ||||||
} | ||||||
|
||||||
// postfixWith appends postfix to every element in the slice. | ||||||
func postfixWith(postfix string, values []string) []string { | ||||||
result := make([]string, len(values)) | ||||||
for i, v := range values { | ||||||
result[i] = v + postfix | ||||||
} | ||||||
return result | ||||||
} | ||||||
|
||||||
func completeLinuxCapabilityNames(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) { | ||||||
return completion.FromList(allLinuxCapabilities()...)(cmd, args, toComplete) | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,9 @@ import ( | |
"strings" | ||
"testing" | ||
|
||
"github.com/docker/cli/internal/test" | ||
"github.com/docker/cli/internal/test/builders" | ||
"github.com/docker/docker/api/types/container" | ||
"github.com/moby/sys/signal" | ||
"github.com/spf13/cobra" | ||
"gotest.tools/v3/assert" | ||
|
@@ -21,13 +24,110 @@ func TestCompleteLinuxCapabilityNames(t *testing.T) { | |
} | ||
} | ||
|
||
func TestCompletePid(t *testing.T) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you decide to change the name then the test should probably also be renamed; |
||
tests := []struct { | ||
containerListFunc func(container.ListOptions) ([]container.Summary, error) | ||
toComplete string | ||
expectedCompletions []string | ||
expectedDirective cobra.ShellCompDirective | ||
}{ | ||
{ | ||
toComplete: "", | ||
expectedCompletions: []string{"container:", "host"}, | ||
expectedDirective: cobra.ShellCompDirectiveNoFileComp, | ||
}, | ||
{ | ||
toComplete: "c", | ||
expectedCompletions: []string{"container:"}, | ||
expectedDirective: cobra.ShellCompDirectiveNoSpace, | ||
}, | ||
{ | ||
containerListFunc: func(container.ListOptions) ([]container.Summary, error) { | ||
return []container.Summary{ | ||
*builders.Container("c1"), | ||
*builders.Container("c2"), | ||
}, nil | ||
}, | ||
toComplete: "container:", | ||
expectedCompletions: []string{"container:c1", "container:c2"}, | ||
expectedDirective: cobra.ShellCompDirectiveNoFileComp, | ||
}, | ||
} | ||
|
||
for _, tc := range tests { | ||
tc := tc | ||
t.Run(tc.toComplete, func(t *testing.T) { | ||
cli := test.NewFakeCli(&fakeClient{ | ||
containerListFunc: tc.containerListFunc, | ||
}) | ||
completions, directive := completePid(cli)(NewRunCommand(cli), nil, tc.toComplete) | ||
assert.Check(t, is.DeepEqual(completions, tc.expectedCompletions)) | ||
assert.Check(t, is.Equal(directive, tc.expectedDirective)) | ||
}) | ||
} | ||
} | ||
|
||
func TestCompleteRestartPolicies(t *testing.T) { | ||
values, directives := completeRestartPolicies(nil, nil, "") | ||
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") | ||
expected := restartPolicies | ||
assert.Check(t, is.DeepEqual(values, expected)) | ||
} | ||
|
||
func TestCompleteSecurityOpts(t *testing.T) { | ||
tests := []struct { | ||
toComplete string | ||
expectedCompletions []string | ||
expectedDirective cobra.ShellCompDirective | ||
}{ | ||
{ | ||
toComplete: "", | ||
expectedCompletions: []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"}, | ||
expectedDirective: cobra.ShellCompDirectiveNoFileComp, | ||
}, | ||
{ | ||
toComplete: "apparmor=", | ||
expectedCompletions: []string{"apparmor="}, | ||
expectedDirective: cobra.ShellCompDirectiveNoSpace, | ||
}, | ||
{ | ||
toComplete: "label=", | ||
expectedCompletions: []string{"label=disable", "label=level:", "label=role:", "label=type:", "label=user:"}, | ||
expectedDirective: cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp, | ||
}, | ||
{ | ||
toComplete: "s", | ||
// We do not filter matching completions but delegate this task to the shell script. | ||
expectedCompletions: []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"}, | ||
expectedDirective: cobra.ShellCompDirectiveNoFileComp, | ||
}, | ||
{ | ||
toComplete: "se", | ||
expectedCompletions: []string{"seccomp="}, | ||
expectedDirective: cobra.ShellCompDirectiveNoSpace, | ||
}, | ||
{ | ||
toComplete: "seccomp=", | ||
expectedCompletions: []string{"seccomp=unconfined"}, | ||
expectedDirective: cobra.ShellCompDirectiveNoFileComp, | ||
}, | ||
{ | ||
toComplete: "sy", | ||
expectedCompletions: []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"}, | ||
expectedDirective: cobra.ShellCompDirectiveNoFileComp, | ||
}, | ||
} | ||
|
||
for _, tc := range tests { | ||
albers marked this conversation as resolved.
Show resolved
Hide resolved
|
||
tc := tc | ||
t.Run(tc.toComplete, func(t *testing.T) { | ||
completions, directive := completeSecurityOpt(nil, nil, tc.toComplete) | ||
assert.Check(t, is.DeepEqual(completions, tc.expectedCompletions)) | ||
assert.Check(t, is.Equal(directive, tc.expectedDirective)) | ||
}) | ||
} | ||
} | ||
|
||
func TestCompleteSignals(t *testing.T) { | ||
values, directives := completeSignals(nil, nil, "") | ||
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh; I was working on this one, when I discovered completion doesn't work for specifying
--attach
multiple times.I started looking "why???" because it does work for some other flags we define multiple times, which is when I discovered the reason; Cobra has some "magic" values for flag "types"; if the flag-type ends with
Slice
orArray
it considers that it can be used multiple times. If not, it considers that the flag can only be set once and it removes it from the suggestions;cli/vendor/github.com/spf13/cobra/completions.go
Lines 402 to 408 in 32ff200
In our case, some types use
list
as name, which isn't matched. I'm considering contributing a patch to Cobra to make it somewhat smarter;list
to the magic stringsBut also to have it match
SliceValue
, which is an interface defined by thepflag
module used by Cobra for flags. I think it's an omission that it doesn't consider that interface;cli/vendor/github.com/spf13/pflag/flag.go
Lines 196 to 203 in 32ff200
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, now I understand.
I was also wondering why the
events --filter
completion only works once.