From 761d76750c8eb9edfc7e13cae8e3db6e3f4db573 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Tue, 22 Oct 2024 19:40:31 +0000 Subject: [PATCH 01/15] Share the container completions Signed-off-by: Harald Albers --- cli/command/container/completion.go | 14 ++++++++++++++ cli/command/container/create.go | 12 ++---------- cli/command/container/run.go | 12 ++---------- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index e6ca17308aee..fb8193b4cafc 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -54,6 +54,20 @@ 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("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("platform", completion.Platforms) + _ = 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)) +} + func completeLinuxCapabilityNames(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) { return completion.FromList(allLinuxCapabilities()...)(cmd, args, toComplete) } diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 31ddeaad0167..00b7436a471a 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -78,16 +78,8 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command { command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled()) 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("platform", completion.Platforms) - _ = 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)) + addCompletions(cmd, dockerCli) + return cmd } diff --git a/cli/command/container/run.go b/cli/command/container/run.go index a3fc5f983a3d..ad8afcab8200 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -69,16 +69,8 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled()) 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("platform", completion.Platforms) - _ = 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)) + addCompletions(cmd, dockerCli) + return cmd } From b598ec8cdb589af30d78c15276b1fe7c02c8247b Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Wed, 23 Oct 2024 13:09:13 +0000 Subject: [PATCH 02/15] Add completion for `--attach` Signed-off-by: Harald Albers --- cli/command/container/completion.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index fb8193b4cafc..53e666445257 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -56,6 +56,7 @@ var restartPolicies = []string{ // 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("env", completion.EnvVarNames) From c555327f0b49fe2addd983cb9a46f51800ab1776 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Thu, 24 Oct 2024 11:08:41 +0000 Subject: [PATCH 03/15] Add completion for `--ipc` Signed-off-by: Harald Albers --- cli/command/container/completion.go | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index 53e666445257..30cb05828154 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -61,6 +61,7 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames) _ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames) _ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames) + _ = cmd.RegisterFlagCompletionFunc("ipc", completeIpc(dockerCLI)) _ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCLI)) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) _ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever)) @@ -69,6 +70,36 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCLI, true)) } +// 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 + } +} + +// 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 +} + func completeLinuxCapabilityNames(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) { return completion.FromList(allLinuxCapabilities()...)(cmd, args, toComplete) } From e513454244f129d04b3a89ea1ad499362dc68040 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Thu, 24 Oct 2024 11:31:25 +0000 Subject: [PATCH 04/15] Add completion for `--link` Signed-off-by: Harald Albers --- cli/command/container/completion.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index 30cb05828154..cffa714003b9 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -62,6 +62,7 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames) _ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames) _ = cmd.RegisterFlagCompletionFunc("ipc", completeIpc(dockerCLI)) + _ = cmd.RegisterFlagCompletionFunc("link", completeLink(dockerCLI)) _ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCLI)) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) _ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever)) @@ -91,6 +92,23 @@ func completeIpc(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command } } +// 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 + } +} + +// 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{} + } + 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)) @@ -100,6 +118,15 @@ func prefixWith(prefix string, values []string) []string { 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) } From ac7bde6f649323fdf7819c52f6e9ef9c34af06f4 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Thu, 24 Oct 2024 13:12:51 +0000 Subject: [PATCH 05/15] Add completion for `--pid` Signed-off-by: Harald Albers --- cli/command/container/completion.go | 15 ++++++++ cli/command/container/completion_test.go | 45 ++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index cffa714003b9..91593c5b3238 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -64,6 +64,7 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = cmd.RegisterFlagCompletionFunc("ipc", completeIpc(dockerCLI)) _ = cmd.RegisterFlagCompletionFunc("link", completeLink(dockerCLI)) _ = 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) @@ -99,6 +100,20 @@ func completeLink(dockerCLI completion.APIClientProvider) func(cmd *cobra.Comman } } +// 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 + } +} + // 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 { diff --git a/cli/command/container/completion_test.go b/cli/command/container/completion_test.go index b85aae32d5dc..62d69b533a78 100644 --- a/cli/command/container/completion_test.go +++ b/cli/command/container/completion_test.go @@ -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,6 +24,48 @@ func TestCompleteLinuxCapabilityNames(t *testing.T) { } } +func TestCompletePid(t *testing.T) { + 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") From 2d89339b34af9fd710520c4e6b43623b131685d6 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Thu, 24 Oct 2024 13:45:23 +0000 Subject: [PATCH 06/15] Add completion for `--storage-opt` Signed-off-by: Harald Albers --- cli/command/container/completion.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index 91593c5b3238..cde6411a425f 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -69,6 +69,7 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever)) _ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies) _ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals) + _ = cmd.RegisterFlagCompletionFunc("storage-opt", completeStorageOpt) _ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCLI, true)) } @@ -114,6 +115,11 @@ func completePid(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command } } +// 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 +} + // 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 { From 5d709a8d9f54fc893ea1cbd4ec71c4cb6f7f3e1e Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Thu, 24 Oct 2024 14:04:15 +0000 Subject: [PATCH 07/15] Add completion for `--ulimit` Signed-off-by: Harald Albers --- cli/command/container/completion.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index cde6411a425f..beb8febea853 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -70,6 +70,7 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies) _ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals) _ = cmd.RegisterFlagCompletionFunc("storage-opt", completeStorageOpt) + _ = cmd.RegisterFlagCompletionFunc("ulimit", completeUlimit) _ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCLI, true)) } @@ -120,6 +121,32 @@ func completeStorageOpt(_ *cobra.Command, _ []string, _ string) ([]string, cobra 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 +} + // 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 { From 3292afe6e611bc7868ec8b927f5f25711c24304b Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Thu, 24 Oct 2024 14:09:32 +0000 Subject: [PATCH 08/15] Add completion for `--userns` Signed-off-by: Harald Albers --- cli/command/container/completion.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index beb8febea853..f988d2c0c112 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -71,6 +71,7 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals) _ = cmd.RegisterFlagCompletionFunc("storage-opt", completeStorageOpt) _ = cmd.RegisterFlagCompletionFunc("ulimit", completeUlimit) + _ = cmd.RegisterFlagCompletionFunc("userns", completion.FromList("host")) _ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCLI, true)) } From 5f7c43e5e66d7026cb27703b08720aeee6a9da5e Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Thu, 24 Oct 2024 20:14:49 +0000 Subject: [PATCH 09/15] Add completion for `--detach-keys` Signed-off-by: Harald Albers --- cli/command/container/completion.go | 5 +++++ cli/command/container/run.go | 1 + 2 files changed, 6 insertions(+) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index f988d2c0c112..23a1dedfd799 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -75,6 +75,11 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCLI, true)) } +// 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 +} + // 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) { diff --git a/cli/command/container/run.go b/cli/command/container/run.go index ad8afcab8200..1077a9994a27 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -69,6 +69,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled()) copts = addFlags(flags) + _ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys) addCompletions(cmd, dockerCli) return cmd From 9a9ae231a94f58fc7a4ef800950d7f31141f4771 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Thu, 24 Oct 2024 21:41:05 +0000 Subject: [PATCH 10/15] Add completion for `--security-opt` Signed-off-by: Harald Albers --- cli/command/container/completion.go | 27 ++++++++++++ cli/command/container/completion_test.go | 53 ++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index 23a1dedfd799..b69c969e717f 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -68,6 +68,7 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = 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) @@ -122,6 +123,32 @@ func completePid(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command } } +// 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 diff --git a/cli/command/container/completion_test.go b/cli/command/container/completion_test.go index 62d69b533a78..0977fdc5192b 100644 --- a/cli/command/container/completion_test.go +++ b/cli/command/container/completion_test.go @@ -73,6 +73,59 @@ func TestCompleteRestartPolicies(t *testing.T) { 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 { + 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") From 3a2503fa433a4f6b61a1b045c90a42537a3c83c8 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Fri, 8 Nov 2024 12:15:28 +0000 Subject: [PATCH 11/15] Add completion for --log-driver` and --log-opt` Signed-off-by: Harald Albers --- cli/command/container/completion.go | 85 +++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index b69c969e717f..5538017f96ad 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -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") @@ -63,6 +122,8 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = 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) @@ -109,6 +170,30 @@ func completeLink(dockerCLI completion.APIClientProvider) func(cmd *cobra.Comman } } +// 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) { From 2915749279e48e262117199405f514df078dc5a4 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Wed, 30 Oct 2024 14:55:19 +0000 Subject: [PATCH 12/15] Add completion for `--uts` Signed-off-by: Harald Albers --- cli/command/container/completion.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index 5538017f96ad..8cc7b71de34d 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -134,6 +134,7 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = cmd.RegisterFlagCompletionFunc("storage-opt", completeStorageOpt) _ = cmd.RegisterFlagCompletionFunc("ulimit", completeUlimit) _ = cmd.RegisterFlagCompletionFunc("userns", completion.FromList("host")) + _ = cmd.RegisterFlagCompletionFunc("uts", completion.FromList("host")) _ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCLI, true)) } From db0ed1e21650d504d1892795ffd0c58f38ba3f3e Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Wed, 30 Oct 2024 15:55:19 +0000 Subject: [PATCH 13/15] Add completion for `--cgroupns` Signed-off-by: Harald Albers --- cli/command/container/completion.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index 8cc7b71de34d..840c6de86e12 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -118,6 +118,7 @@ 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)) @@ -138,6 +139,11 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = 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 From 4525fe37b4736d9e5edfd12676d8e3729bc07e40 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Wed, 30 Oct 2024 16:24:35 +0000 Subject: [PATCH 14/15] Add completion for `--volume-driver` Signed-off-by: Harald Albers --- cli/command/container/completion.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cli/command/container/completion.go b/cli/command/container/completion.go index 840c6de86e12..35a02f4d2e46 100644 --- a/cli/command/container/completion.go +++ b/cli/command/container/completion.go @@ -136,6 +136,7 @@ func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) _ = cmd.RegisterFlagCompletionFunc("ulimit", completeUlimit) _ = cmd.RegisterFlagCompletionFunc("userns", completion.FromList("host")) _ = cmd.RegisterFlagCompletionFunc("uts", completion.FromList("host")) + _ = cmd.RegisterFlagCompletionFunc("volume-driver", completeVolumeDriver(dockerCLI)) _ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCLI, true)) } @@ -272,6 +273,19 @@ func completeUlimit(_ *cobra.Command, _ []string, _ string) ([]string, cobra.She 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 { From 06260e68f3686a0368284a8ce9b910e056bb523b Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Wed, 30 Oct 2024 20:10:37 +0000 Subject: [PATCH 15/15] Handle null completions with a default callback Credits to thaJeztah Signed-off-by: Harald Albers --- cli/command/container/create.go | 7 +++++++ cli/command/container/run.go | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 00b7436a471a..9f73b7f0afe3 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -80,6 +80,13 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command { addCompletions(cmd, dockerCli) + flags.VisitAll(func(flag *pflag.Flag) { + // Set a default completion function if none was set. We don't look + // up if it does already have one set, because Cobra does this for + // us, and returns an error (which we ignore for this reason). + _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + }) + return cmd } diff --git a/cli/command/container/run.go b/cli/command/container/run.go index 1077a9994a27..feaf59dd786c 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -72,6 +72,13 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { _ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys) addCompletions(cmd, dockerCli) + flags.VisitAll(func(flag *pflag.Flag) { + // Set a default completion function if none was set. We don't look + // up if it does already have one set, because Cobra does this for + // us, and returns an error (which we ignore for this reason). + _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete) + }) + return cmd }