-
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
Changes from all commits
761d767
b598ec8
c555327
e513454
ac7bde6
2d89339
5d709a8
3292afe
5f7c43e
9a9ae23
3a2503f
2915749
db0ed1e
4525fe3
06260e6
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,207 @@ 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)) | ||||||
} | ||||||
|
||||||
// completeCgroupns implements shell completion for the `--cgroupns` option of `run` and `create`. | ||||||
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
+292
to
+295
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,108 @@ 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 { | ||
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 TestCompleteSecurityOpt(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
|
||
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.