From 616b7c974bc7cb020db233c3c552dabbdffee1f8 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Tue, 15 Oct 2024 19:05:36 +0000 Subject: [PATCH 1/9] [WIP] Completion for events --filter Signed-off-by: Harald Albers --- cli/command/system/completion.go | 97 ++++++++++++++++++++++++++++++++ cli/command/system/events.go | 2 + 2 files changed, 99 insertions(+) create mode 100644 cli/command/system/completion.go diff --git a/cli/command/system/completion.go b/cli/command/system/completion.go new file mode 100644 index 0000000000..21e0623f98 --- /dev/null +++ b/cli/command/system/completion.go @@ -0,0 +1,97 @@ +package system + +import ( + "strings" + + "github.com/docker/cli/cli/command/completion" + "github.com/spf13/cobra" +) + +var ( + eventFilters = []string{"container", "daemon", "event", "image", "label", "network", "node", "scope", "type", "volume"} + eventNames = []string{ + "attach", + "commit", + "connect", + "copy", + "create", + "delete", + "destroy", + "detach", + "die", + "disable", + "disconnect", + "enable", + "exec_create", + "exec_detach", + "exec_die", + "exec_start", + "export", + "health_status", + "import", + "install", + "kill", + "load", + "mount", + "oom", + "pause", + "pull", + "push", + "reload", + "remove", + "rename", + "resize", + "restart", + "save", + "start", + "stop", + "tag", + "top", + "unmount", + "unpause", + "untag", + "update", + } +) +var eventTypes = []string{"config", "container", "daemon", "image", "network", "node", "plugin", "secret", "service", "volume"} + +func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArgsFn { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if strings.HasPrefix(toComplete, "container=") { + // the pure container name list should be pulled out from ContainerNames. + names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete) + return prefixWith("container=", names), cobra.ShellCompDirectiveDefault + } + if strings.HasPrefix(toComplete, "event=") { + return prefixWith("event=", eventNames), cobra.ShellCompDirectiveDefault + } + if strings.HasPrefix(toComplete, "label=") { + return nil, cobra.ShellCompDirectiveNoFileComp + } + if strings.HasPrefix(toComplete, "network=") { + // the pure network name list should be pulled out from NetworkNames. + names, _ := completion.NetworkNames(dockerCLI)(cmd, args, toComplete) + return prefixWith("network=", names), cobra.ShellCompDirectiveDefault + } + if strings.HasPrefix(toComplete, "type=") { + return prefixWith("type=", eventTypes), cobra.ShellCompDirectiveDefault + } + return postfixWith("=", eventFilters), cobra.ShellCompDirectiveNoSpace + } +} + +func prefixWith(prefix string, values []string) []string { + result := make([]string, len(values)) + for i, v := range values { + result[i] = prefix + v + } + return result +} + +func postfixWith(postfix string, values []string) []string { + result := make([]string, len(values)) + for i, v := range values { + result[i] = v + postfix + } + return result +} diff --git a/cli/command/system/events.go b/cli/command/system/events.go index a54d30ffed..e31256e215 100644 --- a/cli/command/system/events.go +++ b/cli/command/system/events.go @@ -50,6 +50,8 @@ func NewEventsCommand(dockerCli command.Cli) *cobra.Command { flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") flags.StringVar(&options.format, "format", "", flagsHelper.InspectFormatHelp) // using the same flag description as "inspect" commands for now. + _ = cmd.RegisterFlagCompletionFunc("filter", completeFilters(dockerCli)) + return cmd } From a09c2047d4ab035c91167dffea0fae5695da637e Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Thu, 17 Oct 2024 15:20:18 +0000 Subject: [PATCH 2/9] [WIP] Refactor network name completion Signed-off-by: Harald Albers --- cli/command/completion/functions.go | 23 ++++++++++++++++------- cli/command/container/create.go | 2 +- cli/command/container/run.go | 2 +- cli/command/network/connect.go | 2 +- cli/command/network/disconnect.go | 2 +- cli/command/network/inspect.go | 2 +- cli/command/network/remove.go | 2 +- cli/command/system/completion.go | 6 ++++-- 8 files changed, 26 insertions(+), 15 deletions(-) diff --git a/cli/command/completion/functions.go b/cli/command/completion/functions.go index b217ec54b0..dc17619237 100644 --- a/cli/command/completion/functions.go +++ b/cli/command/completion/functions.go @@ -91,21 +91,30 @@ func VolumeNames(dockerCLI APIClientProvider) ValidArgsFn { } } -// NetworkNames offers completion for networks -func NetworkNames(dockerCLI APIClientProvider) ValidArgsFn { +// CompleteNetworkNames offers completion for networks +func CompleteNetworkNames(dockerCLI APIClientProvider) ValidArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list, err := dockerCLI.Client().NetworkList(cmd.Context(), network.ListOptions{}) + names, err := NetworkNames(dockerCLI, cmd) if err != nil { return nil, cobra.ShellCompDirectiveError } - var names []string - for _, nw := range list { - names = append(names, nw.Name) - } return names, cobra.ShellCompDirectiveNoFileComp } } +// NetworkNames returns a list of networks +func NetworkNames(dockerCLI APIClientProvider, cmd *cobra.Command) ([]string, error) { + summaries, err := dockerCLI.Client().NetworkList(cmd.Context(), network.ListOptions{}) + if err != nil { + return nil, err + } + var names []string + for _, nws := range summaries { + names = append(names, nws.Name) + } + return names, nil +} + // EnvVarNames offers completion for environment-variable names. This // completion can be used for "--env" and "--build-arg" flags, which // allow obtaining the value of the given environment-variable if present diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 31ddeaad01..63a5fec7f5 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -82,7 +82,7 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command { _ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames) _ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames) _ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames) - _ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli)) + _ = cmd.RegisterFlagCompletionFunc("network", completion.CompleteNetworkNames(dockerCli)) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) _ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever)) _ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies) diff --git a/cli/command/container/run.go b/cli/command/container/run.go index a3fc5f983a..11e4907784 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -73,7 +73,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { _ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames) _ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames) _ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames) - _ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli)) + _ = cmd.RegisterFlagCompletionFunc("network", completion.CompleteNetworkNames(dockerCli)) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) _ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever)) _ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies) diff --git a/cli/command/network/connect.go b/cli/command/network/connect.go index 0d9e871259..b2eced5766 100644 --- a/cli/command/network/connect.go +++ b/cli/command/network/connect.go @@ -41,7 +41,7 @@ func newConnectCommand(dockerCLI command.Cli) *cobra.Command { }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return completion.NetworkNames(dockerCLI)(cmd, args, toComplete) + return completion.CompleteNetworkNames(dockerCLI)(cmd, args, toComplete) } nw := args[0] return completion.ContainerNames(dockerCLI, true, not(isConnected(nw)))(cmd, args, toComplete) diff --git a/cli/command/network/disconnect.go b/cli/command/network/disconnect.go index 521aee33f5..37e13acb8b 100644 --- a/cli/command/network/disconnect.go +++ b/cli/command/network/disconnect.go @@ -31,7 +31,7 @@ func newDisconnectCommand(dockerCli command.Cli) *cobra.Command { }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return completion.NetworkNames(dockerCli)(cmd, args, toComplete) + return completion.CompleteNetworkNames(dockerCli)(cmd, args, toComplete) } network := args[0] return completion.ContainerNames(dockerCli, true, isConnected(network))(cmd, args, toComplete) diff --git a/cli/command/network/inspect.go b/cli/command/network/inspect.go index 72f66382fa..1cb58c9189 100644 --- a/cli/command/network/inspect.go +++ b/cli/command/network/inspect.go @@ -34,7 +34,7 @@ func newInspectCommand(dockerCLI command.Cli) *cobra.Command { opts.names = args return runInspect(cmd.Context(), dockerCLI.Client(), dockerCLI.Out(), opts) }, - ValidArgsFunction: completion.NetworkNames(dockerCLI), + ValidArgsFunction: completion.CompleteNetworkNames(dockerCLI), } cmd.Flags().StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp) diff --git a/cli/command/network/remove.go b/cli/command/network/remove.go index 105e629f61..e46b7dd716 100644 --- a/cli/command/network/remove.go +++ b/cli/command/network/remove.go @@ -27,7 +27,7 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runRemove(cmd.Context(), dockerCli, args, &opts) }, - ValidArgsFunction: completion.NetworkNames(dockerCli), + ValidArgsFunction: completion.CompleteNetworkNames(dockerCli), } flags := cmd.Flags() diff --git a/cli/command/system/completion.go b/cli/command/system/completion.go index 21e0623f98..4cde187363 100644 --- a/cli/command/system/completion.go +++ b/cli/command/system/completion.go @@ -69,8 +69,10 @@ func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArg return nil, cobra.ShellCompDirectiveNoFileComp } if strings.HasPrefix(toComplete, "network=") { - // the pure network name list should be pulled out from NetworkNames. - names, _ := completion.NetworkNames(dockerCLI)(cmd, args, toComplete) + names, err := completion.NetworkNames(dockerCLI, cmd) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } return prefixWith("network=", names), cobra.ShellCompDirectiveDefault } if strings.HasPrefix(toComplete, "type=") { From c17b87a7139a72ea97a80abb312d5331e0090db9 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Thu, 17 Oct 2024 21:21:45 +0000 Subject: [PATCH 3/9] Revert "[WIP] Refactor network name completion" This reverts commit a09c2047d4ab035c91167dffea0fae5695da637e. Signed-off-by: Harald Albers --- cli/command/completion/functions.go | 23 +++++++---------------- cli/command/container/create.go | 2 +- cli/command/container/run.go | 2 +- cli/command/network/connect.go | 2 +- cli/command/network/disconnect.go | 2 +- cli/command/network/inspect.go | 2 +- cli/command/network/remove.go | 2 +- cli/command/system/completion.go | 6 ++---- 8 files changed, 15 insertions(+), 26 deletions(-) diff --git a/cli/command/completion/functions.go b/cli/command/completion/functions.go index dc17619237..b217ec54b0 100644 --- a/cli/command/completion/functions.go +++ b/cli/command/completion/functions.go @@ -91,30 +91,21 @@ func VolumeNames(dockerCLI APIClientProvider) ValidArgsFn { } } -// CompleteNetworkNames offers completion for networks -func CompleteNetworkNames(dockerCLI APIClientProvider) ValidArgsFn { +// NetworkNames offers completion for networks +func NetworkNames(dockerCLI APIClientProvider) ValidArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - names, err := NetworkNames(dockerCLI, cmd) + list, err := dockerCLI.Client().NetworkList(cmd.Context(), network.ListOptions{}) if err != nil { return nil, cobra.ShellCompDirectiveError } + var names []string + for _, nw := range list { + names = append(names, nw.Name) + } return names, cobra.ShellCompDirectiveNoFileComp } } -// NetworkNames returns a list of networks -func NetworkNames(dockerCLI APIClientProvider, cmd *cobra.Command) ([]string, error) { - summaries, err := dockerCLI.Client().NetworkList(cmd.Context(), network.ListOptions{}) - if err != nil { - return nil, err - } - var names []string - for _, nws := range summaries { - names = append(names, nws.Name) - } - return names, nil -} - // EnvVarNames offers completion for environment-variable names. This // completion can be used for "--env" and "--build-arg" flags, which // allow obtaining the value of the given environment-variable if present diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 63a5fec7f5..31ddeaad01 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -82,7 +82,7 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command { _ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames) _ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames) _ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames) - _ = cmd.RegisterFlagCompletionFunc("network", completion.CompleteNetworkNames(dockerCli)) + _ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli)) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) _ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever)) _ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies) diff --git a/cli/command/container/run.go b/cli/command/container/run.go index 11e4907784..a3fc5f983a 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -73,7 +73,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { _ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames) _ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames) _ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames) - _ = cmd.RegisterFlagCompletionFunc("network", completion.CompleteNetworkNames(dockerCli)) + _ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli)) _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) _ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever)) _ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies) diff --git a/cli/command/network/connect.go b/cli/command/network/connect.go index b2eced5766..0d9e871259 100644 --- a/cli/command/network/connect.go +++ b/cli/command/network/connect.go @@ -41,7 +41,7 @@ func newConnectCommand(dockerCLI command.Cli) *cobra.Command { }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return completion.CompleteNetworkNames(dockerCLI)(cmd, args, toComplete) + return completion.NetworkNames(dockerCLI)(cmd, args, toComplete) } nw := args[0] return completion.ContainerNames(dockerCLI, true, not(isConnected(nw)))(cmd, args, toComplete) diff --git a/cli/command/network/disconnect.go b/cli/command/network/disconnect.go index 37e13acb8b..521aee33f5 100644 --- a/cli/command/network/disconnect.go +++ b/cli/command/network/disconnect.go @@ -31,7 +31,7 @@ func newDisconnectCommand(dockerCli command.Cli) *cobra.Command { }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return completion.CompleteNetworkNames(dockerCli)(cmd, args, toComplete) + return completion.NetworkNames(dockerCli)(cmd, args, toComplete) } network := args[0] return completion.ContainerNames(dockerCli, true, isConnected(network))(cmd, args, toComplete) diff --git a/cli/command/network/inspect.go b/cli/command/network/inspect.go index 1cb58c9189..72f66382fa 100644 --- a/cli/command/network/inspect.go +++ b/cli/command/network/inspect.go @@ -34,7 +34,7 @@ func newInspectCommand(dockerCLI command.Cli) *cobra.Command { opts.names = args return runInspect(cmd.Context(), dockerCLI.Client(), dockerCLI.Out(), opts) }, - ValidArgsFunction: completion.CompleteNetworkNames(dockerCLI), + ValidArgsFunction: completion.NetworkNames(dockerCLI), } cmd.Flags().StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp) diff --git a/cli/command/network/remove.go b/cli/command/network/remove.go index e46b7dd716..105e629f61 100644 --- a/cli/command/network/remove.go +++ b/cli/command/network/remove.go @@ -27,7 +27,7 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { return runRemove(cmd.Context(), dockerCli, args, &opts) }, - ValidArgsFunction: completion.CompleteNetworkNames(dockerCli), + ValidArgsFunction: completion.NetworkNames(dockerCli), } flags := cmd.Flags() diff --git a/cli/command/system/completion.go b/cli/command/system/completion.go index 4cde187363..21e0623f98 100644 --- a/cli/command/system/completion.go +++ b/cli/command/system/completion.go @@ -69,10 +69,8 @@ func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArg return nil, cobra.ShellCompDirectiveNoFileComp } if strings.HasPrefix(toComplete, "network=") { - names, err := completion.NetworkNames(dockerCLI, cmd) - if err != nil { - return nil, cobra.ShellCompDirectiveError - } + // the pure network name list should be pulled out from NetworkNames. + names, _ := completion.NetworkNames(dockerCLI)(cmd, args, toComplete) return prefixWith("network=", names), cobra.ShellCompDirectiveDefault } if strings.HasPrefix(toComplete, "type=") { From 3f3ff40ee4f46b3402ee8c7846dd7d262c2eb785 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Thu, 17 Oct 2024 21:48:18 +0000 Subject: [PATCH 4/9] Add error handling Signed-off-by: Harald Albers --- cli/command/system/completion.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cli/command/system/completion.go b/cli/command/system/completion.go index 21e0623f98..4e52f88e5e 100644 --- a/cli/command/system/completion.go +++ b/cli/command/system/completion.go @@ -52,14 +52,16 @@ var ( "untag", "update", } + eventTypes = []string{"config", "container", "daemon", "image", "network", "node", "plugin", "secret", "service", "volume"} ) -var eventTypes = []string{"config", "container", "daemon", "image", "network", "node", "plugin", "secret", "service", "volume"} func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if strings.HasPrefix(toComplete, "container=") { - // the pure container name list should be pulled out from ContainerNames. names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete) + if names == nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } return prefixWith("container=", names), cobra.ShellCompDirectiveDefault } if strings.HasPrefix(toComplete, "event=") { @@ -69,8 +71,10 @@ func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArg return nil, cobra.ShellCompDirectiveNoFileComp } if strings.HasPrefix(toComplete, "network=") { - // the pure network name list should be pulled out from NetworkNames. names, _ := completion.NetworkNames(dockerCLI)(cmd, args, toComplete) + if names == nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } return prefixWith("network=", names), cobra.ShellCompDirectiveDefault } if strings.HasPrefix(toComplete, "type=") { From c5306f3c1ab4f5020ce7ee213c615802447441eb Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Fri, 18 Oct 2024 14:28:30 +0000 Subject: [PATCH 5/9] Use existing constants in completion of types and events in `events --filter` Signed-off-by: Harald Albers --- cli/command/system/completion.go | 140 +++++++++++++++++++++---------- 1 file changed, 95 insertions(+), 45 deletions(-) diff --git a/cli/command/system/completion.go b/cli/command/system/completion.go index 4e52f88e5e..ca0c85d883 100644 --- a/cli/command/system/completion.go +++ b/cli/command/system/completion.go @@ -3,56 +3,82 @@ package system import ( "strings" + "github.com/docker/docker/api/types/events" + "github.com/docker/cli/cli/command/completion" "github.com/spf13/cobra" ) var ( eventFilters = []string{"container", "daemon", "event", "image", "label", "network", "node", "scope", "type", "volume"} - eventNames = []string{ - "attach", - "commit", - "connect", - "copy", - "create", - "delete", - "destroy", - "detach", - "die", - "disable", - "disconnect", - "enable", - "exec_create", - "exec_detach", - "exec_die", - "exec_start", - "export", - "health_status", - "import", - "install", - "kill", - "load", - "mount", - "oom", - "pause", - "pull", - "push", - "reload", - "remove", - "rename", - "resize", - "restart", - "save", - "start", - "stop", - "tag", - "top", - "unmount", - "unpause", - "untag", - "update", + + // eventTypes is a list of all event types. + // This should be moved to the moby codebase once its usage is consolidated here. + eventTypes = []events.Type{ + events.BuilderEventType, + events.ConfigEventType, + events.ContainerEventType, + events.DaemonEventType, + events.ImageEventType, + events.NetworkEventType, + events.NodeEventType, + events.PluginEventType, + events.SecretEventType, + events.ServiceEventType, + events.VolumeEventType, + } + + // eventActions is a list of all event actions. + // This should be moved to the moby codebase once its usage is consolidated here. + eventActions = []events.Action{ + events.ActionCreate, + events.ActionStart, + events.ActionRestart, + events.ActionStop, + events.ActionCheckpoint, + events.ActionPause, + events.ActionUnPause, + events.ActionAttach, + events.ActionDetach, + events.ActionResize, + events.ActionUpdate, + events.ActionRename, + events.ActionKill, + events.ActionDie, + events.ActionOOM, + events.ActionDestroy, + events.ActionRemove, + events.ActionCommit, + events.ActionTop, + events.ActionCopy, + events.ActionArchivePath, + events.ActionExtractToDir, + events.ActionExport, + events.ActionImport, + events.ActionSave, + events.ActionLoad, + events.ActionTag, + events.ActionUnTag, + events.ActionPush, + events.ActionPull, + events.ActionPrune, + events.ActionDelete, + events.ActionEnable, + events.ActionDisable, + events.ActionConnect, + events.ActionDisconnect, + events.ActionReload, + events.ActionMount, + events.ActionUnmount, + events.ActionExecCreate, + events.ActionExecStart, + events.ActionExecDie, + events.ActionExecDetach, + events.ActionHealthStatus, + events.ActionHealthStatusRunning, + events.ActionHealthStatusHealthy, + events.ActionHealthStatusUnhealthy, } - eventTypes = []string{"config", "container", "daemon", "image", "network", "node", "plugin", "secret", "service", "volume"} ) func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArgsFn { @@ -65,7 +91,7 @@ func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArg return prefixWith("container=", names), cobra.ShellCompDirectiveDefault } if strings.HasPrefix(toComplete, "event=") { - return prefixWith("event=", eventNames), cobra.ShellCompDirectiveDefault + return prefixWith("event=", validEventNames()), cobra.ShellCompDirectiveDefault } if strings.HasPrefix(toComplete, "label=") { return nil, cobra.ShellCompDirectiveNoFileComp @@ -78,7 +104,7 @@ func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArg return prefixWith("network=", names), cobra.ShellCompDirectiveDefault } if strings.HasPrefix(toComplete, "type=") { - return prefixWith("type=", eventTypes), cobra.ShellCompDirectiveDefault + return prefixWith("type=", eventTypeNames()), cobra.ShellCompDirectiveDefault } return postfixWith("=", eventFilters), cobra.ShellCompDirectiveNoSpace } @@ -99,3 +125,27 @@ func postfixWith(postfix string, values []string) []string { } return result } + +// eventTypeNames provides a list of all event types. +// The list is derived from eventTypes. +func eventTypeNames() []string { + names := make([]string, len(eventTypes)) + for i, eventType := range eventTypes { + names[i] = string(eventType) + } + return names +} + +// validEventNames provides a list of all event actions. +// The list is derived from eventActions. +// Actions that are not suitable for usage in completions are removed. +func validEventNames() []string { + names := []string{} + for _, eventAction := range eventActions { + if strings.Contains(string(eventAction), " ") { + continue + } + names = append(names, string(eventAction)) + } + return names +} From c7af8da7b9de422cf920f9fa6f86a133c0af5568 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Fri, 18 Oct 2024 14:46:50 +0000 Subject: [PATCH 6/9] Add remaining completions for `events --filter` Signed-off-by: Harald Albers --- cli/command/system/completion.go | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/cli/command/system/completion.go b/cli/command/system/completion.go index ca0c85d883..c84cdf22be 100644 --- a/cli/command/system/completion.go +++ b/cli/command/system/completion.go @@ -1,6 +1,7 @@ package system import ( + "github.com/docker/docker/api/types" "strings" "github.com/docker/docker/api/types/events" @@ -81,6 +82,7 @@ var ( } ) +// completeFilters provides completion for the filters that can be used with `--filter`. func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if strings.HasPrefix(toComplete, "container=") { @@ -90,9 +92,24 @@ func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArg } return prefixWith("container=", names), cobra.ShellCompDirectiveDefault } + if strings.HasPrefix(toComplete, "daemon=") { + info, err := dockerCLI.Client().Info(cmd.Context()) + if err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + completions := []string{info.ID, info.Name} + return prefixWith("daemon=", completions), cobra.ShellCompDirectiveNoFileComp + } if strings.HasPrefix(toComplete, "event=") { return prefixWith("event=", validEventNames()), cobra.ShellCompDirectiveDefault } + if strings.HasPrefix(toComplete, "image=") { + names, _ := completion.ImageNames(dockerCLI)(cmd, args, toComplete) + if names == nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return prefixWith("image=", names), cobra.ShellCompDirectiveDefault + } if strings.HasPrefix(toComplete, "label=") { return nil, cobra.ShellCompDirectiveNoFileComp } @@ -103,9 +120,23 @@ func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArg } return prefixWith("network=", names), cobra.ShellCompDirectiveDefault } + if strings.HasPrefix(toComplete, "node=") { + return prefixWith("node=", nodeNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp + } + if strings.HasPrefix(toComplete, "scope=") { + return prefixWith("scope=", []string{"local", "swarm"}), cobra.ShellCompDirectiveNoFileComp + } if strings.HasPrefix(toComplete, "type=") { return prefixWith("type=", eventTypeNames()), cobra.ShellCompDirectiveDefault } + if strings.HasPrefix(toComplete, "volume=") { + names, _ := completion.VolumeNames(dockerCLI)(cmd, args, toComplete) + if names == nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return prefixWith("volume=", names), cobra.ShellCompDirectiveDefault + } + return postfixWith("=", eventFilters), cobra.ShellCompDirectiveNoSpace } } @@ -149,3 +180,17 @@ func validEventNames() []string { } return names } + +// nodeNames contacts the API to get a list of nodes. +// In case of an error, an empty list is returned. +func nodeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string { + nodes, err := dockerCLI.Client().NodeList(cmd.Context(), types.NodeListOptions{}) + if err != nil { + return []string{} + } + names := []string{} + for _, node := range nodes { + names = append(names, node.Description.Hostname) + } + return names +} From 8ae6ad62566fe43864f289790907c1a422f63f4f Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Fri, 18 Oct 2024 15:57:19 +0000 Subject: [PATCH 7/9] Refactor API helper functions Signed-off-by: Harald Albers --- cli/command/system/completion.go | 115 +++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 35 deletions(-) diff --git a/cli/command/system/completion.go b/cli/command/system/completion.go index c84cdf22be..f91269b59d 100644 --- a/cli/command/system/completion.go +++ b/cli/command/system/completion.go @@ -1,9 +1,13 @@ package system import ( - "github.com/docker/docker/api/types" "strings" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/volume" + "github.com/docker/docker/api/types/events" "github.com/docker/cli/cli/command/completion" @@ -86,39 +90,22 @@ var ( func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if strings.HasPrefix(toComplete, "container=") { - names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete) - if names == nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - return prefixWith("container=", names), cobra.ShellCompDirectiveDefault + return prefixWith("container=", containerNames(dockerCLI, cmd, args, toComplete)), cobra.ShellCompDirectiveNoFileComp } if strings.HasPrefix(toComplete, "daemon=") { - info, err := dockerCLI.Client().Info(cmd.Context()) - if err != nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - completions := []string{info.ID, info.Name} - return prefixWith("daemon=", completions), cobra.ShellCompDirectiveNoFileComp + return prefixWith("daemon=", daemonNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp } if strings.HasPrefix(toComplete, "event=") { - return prefixWith("event=", validEventNames()), cobra.ShellCompDirectiveDefault + return prefixWith("event=", validEventNames()), cobra.ShellCompDirectiveNoFileComp } if strings.HasPrefix(toComplete, "image=") { - names, _ := completion.ImageNames(dockerCLI)(cmd, args, toComplete) - if names == nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - return prefixWith("image=", names), cobra.ShellCompDirectiveDefault + return prefixWith("image=", imageNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp } if strings.HasPrefix(toComplete, "label=") { return nil, cobra.ShellCompDirectiveNoFileComp } if strings.HasPrefix(toComplete, "network=") { - names, _ := completion.NetworkNames(dockerCLI)(cmd, args, toComplete) - if names == nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - return prefixWith("network=", names), cobra.ShellCompDirectiveDefault + return prefixWith("network=", networkNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp } if strings.HasPrefix(toComplete, "node=") { return prefixWith("node=", nodeNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp @@ -127,14 +114,10 @@ func completeFilters(dockerCLI completion.APIClientProvider) completion.ValidArg return prefixWith("scope=", []string{"local", "swarm"}), cobra.ShellCompDirectiveNoFileComp } if strings.HasPrefix(toComplete, "type=") { - return prefixWith("type=", eventTypeNames()), cobra.ShellCompDirectiveDefault + return prefixWith("type=", eventTypeNames()), cobra.ShellCompDirectiveNoFileComp } if strings.HasPrefix(toComplete, "volume=") { - names, _ := completion.VolumeNames(dockerCLI)(cmd, args, toComplete) - if names == nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - return prefixWith("volume=", names), cobra.ShellCompDirectiveDefault + return prefixWith("volume=", volumeNames(dockerCLI, cmd)), cobra.ShellCompDirectiveNoFileComp } return postfixWith("=", eventFilters), cobra.ShellCompDirectiveNoSpace @@ -171,7 +154,7 @@ func eventTypeNames() []string { // The list is derived from eventActions. // Actions that are not suitable for usage in completions are removed. func validEventNames() []string { - names := []string{} + var names []string for _, eventAction := range eventActions { if strings.Contains(string(eventAction), " ") { continue @@ -181,16 +164,78 @@ func validEventNames() []string { return names } -// nodeNames contacts the API to get a list of nodes. +// containerNames contacts the API to get names and optionally IDs of containers. // In case of an error, an empty list is returned. -func nodeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string { - nodes, err := dockerCLI.Client().NodeList(cmd.Context(), types.NodeListOptions{}) +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 +} + +// daemonNames contacts the API to get name and ID of the current docker daemon. +// In case of an error, an empty list is returned. +func daemonNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string { + info, err := dockerCLI.Client().Info(cmd.Context()) if err != nil { return []string{} } - names := []string{} - for _, node := range nodes { + return []string{info.Name, info.ID} +} + +// imageNames contacts the API to get a list of image names. +// In case of an error, an empty list is returned. +func imageNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string { + list, err := dockerCLI.Client().ImageList(cmd.Context(), image.ListOptions{}) + if err != nil { + return []string{} + } + var names []string + for _, img := range list { + names = append(names, img.RepoTags...) + } + return names +} + +// networkNames contacts the API to get a list of network names. +// In case of an error, an empty list is returned. +func networkNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string { + list, err := dockerCLI.Client().NetworkList(cmd.Context(), network.ListOptions{}) + if err != nil { + return []string{} + } + var names []string + for _, nw := range list { + names = append(names, nw.Name) + } + return names +} + +// nodeNames contacts the API to get a list of node names. +// In case of an error, an empty list is returned. +func nodeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string { + list, err := dockerCLI.Client().NodeList(cmd.Context(), types.NodeListOptions{}) + if err != nil { + return []string{} + } + var names []string + for _, node := range list { names = append(names, node.Description.Hostname) } return names } + +// volumeNames contacts the API to get a list of volume names. +// In case of an error, an empty list is returned. +func volumeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string { + list, err := dockerCLI.Client().VolumeList(cmd.Context(), volume.ListOptions{}) + if err != nil { + return []string{} + } + var names []string + for _, v := range list.Volumes { + names = append(names, v.Name) + } + return names +} From 4f7abb8db6f3ded4d4b4be929f9288daefb0989c Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Sat, 19 Oct 2024 16:00:41 +0000 Subject: [PATCH 8/9] Fix linter errors Signed-off-by: Harald Albers --- cli/command/system/completion.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/command/system/completion.go b/cli/command/system/completion.go index f91269b59d..eaad9b343f 100644 --- a/cli/command/system/completion.go +++ b/cli/command/system/completion.go @@ -154,7 +154,7 @@ func eventTypeNames() []string { // The list is derived from eventActions. // Actions that are not suitable for usage in completions are removed. func validEventNames() []string { - var names []string + names := []string{} for _, eventAction := range eventActions { if strings.Contains(string(eventAction), " ") { continue @@ -191,7 +191,7 @@ func imageNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []st if err != nil { return []string{} } - var names []string + names := []string{} for _, img := range list { names = append(names, img.RepoTags...) } @@ -205,7 +205,7 @@ func networkNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) [] if err != nil { return []string{} } - var names []string + names := []string{} for _, nw := range list { names = append(names, nw.Name) } @@ -219,7 +219,7 @@ func nodeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []str if err != nil { return []string{} } - var names []string + names := []string{} for _, node := range list { names = append(names, node.Description.Hostname) } @@ -233,7 +233,7 @@ func volumeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []s if err != nil { return []string{} } - var names []string + names := []string{} for _, v := range list.Volumes { names = append(names, v.Name) } From 3eaad535b3a21e982304d055549627636652d6ce Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Sat, 19 Oct 2024 16:00:41 +0000 Subject: [PATCH 9/9] Add tests for `events --filter container=` Signed-off-by: Harald Albers --- cli/command/system/client_test.go | 8 +++++ cli/command/system/completion_test.go | 45 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 cli/command/system/completion_test.go diff --git a/cli/command/system/client_test.go b/cli/command/system/client_test.go index b6eeb3bd92..3c9aa290e7 100644 --- a/cli/command/system/client_test.go +++ b/cli/command/system/client_test.go @@ -19,6 +19,7 @@ type fakeClient struct { eventsFn func(context.Context, events.ListOptions) (<-chan events.Message, <-chan error) containerPruneFunc func(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) networkPruneFunc func(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error) + containerListFunc func(context.Context, container.ListOptions) ([]container.Summary, error) } func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) { @@ -46,3 +47,10 @@ func (cli *fakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Ar } return network.PruneReport{}, nil } + +func (cli *fakeClient) ContainerList(ctx context.Context, options container.ListOptions) ([]container.Summary, error) { + if cli.containerListFunc != nil { + return cli.containerListFunc(ctx, options) + } + return []container.Summary{}, nil +} diff --git a/cli/command/system/completion_test.go b/cli/command/system/completion_test.go new file mode 100644 index 0000000000..59aa546a08 --- /dev/null +++ b/cli/command/system/completion_test.go @@ -0,0 +1,45 @@ +package system + +import ( + "context" + "errors" + "testing" + + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/builders" + "github.com/docker/docker/api/types/container" + "github.com/spf13/cobra" + "gotest.tools/v3/assert" +) + +// Successful completion lists all container names, prefixed with "container=". +// Filtering the completions by the current word is delegated to the completion script. +func TestCompleteEventFilterContainer(t *testing.T) { + cli := test.NewFakeCli(&fakeClient{ + containerListFunc: func(_ context.Context, _ container.ListOptions) ([]container.Summary, error) { + return []container.Summary{ + *builders.Container("foo"), + *builders.Container("bar"), + }, nil + }, + }) + + completions, directive := completeFilters(cli)(NewEventsCommand(cli), nil, "container=") + + assert.DeepEqual(t, completions, []string{"container=foo", "container=bar"}) + assert.Equal(t, directive, cobra.ShellCompDirectiveNoFileComp) +} + +// In case of API errors, no completions are returned. +func TestCompleteEventFilterContainerAPIError(t *testing.T) { + cli := test.NewFakeCli(&fakeClient{ + containerListFunc: func(_ context.Context, _ container.ListOptions) ([]container.Summary, error) { + return nil, errors.New("API error") + }, + }) + + completions, directive := completeFilters(cli)(NewEventsCommand(cli), nil, "container=") + + assert.DeepEqual(t, completions, []string{}) + assert.Equal(t, directive, cobra.ShellCompDirectiveNoFileComp) +}