From 8bb5595f28aa98de4edae38cdec5b33a8d71230c Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 10 Sep 2025 00:25:24 +0200 Subject: [PATCH] cli/command/system: add shell completion for "docker inspect" The "docker inspect" command can inspect any type of object, which would require all possible endpoints to be contacted. By default, we don't provide completion, but if a `--type` is passed, we provide completion for the given type. For example, `docker inspect --type container` will complete container names, `docker inspect --type volume` will complete volume names and so on. Signed-off-by: Sebastiaan van Stijn --- cli/command/system/completion.go | 120 +++++++++++++++++++++++++++++++ cli/command/system/inspect.go | 2 +- 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/cli/command/system/completion.go b/cli/command/system/completion.go index 8b3c1fc5b..544a0e029 100644 --- a/cli/command/system/completion.go +++ b/cli/command/system/completion.go @@ -1,10 +1,14 @@ package system import ( + "fmt" "strings" "github.com/docker/cli/cli/command/completion" + "github.com/docker/cli/cli/command/idresolver" "github.com/moby/moby/api/types/events" + "github.com/moby/moby/api/types/filters" + "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/client" "github.com/spf13/cobra" ) @@ -157,6 +161,20 @@ func validEventNames() []string { return names } +// configNames contacts the API to get a list of config names. +// In case of an error, an empty list is returned. +func configNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string { + list, err := dockerCLI.Client().ConfigList(cmd.Context(), client.ConfigListOptions{}) + if err != nil { + return []string{} + } + names := make([]string, 0, len(list)) + for _, v := range list { + names = append(names, v.Spec.Name) + } + return names +} + // 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 { @@ -219,6 +237,72 @@ func nodeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []str return names } +// pluginNames contacts the API to get a list of plugin names. +// In case of an error, an empty list is returned. +func pluginNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string { + list, err := dockerCLI.Client().PluginList(cmd.Context(), filters.Args{}) + if err != nil { + return []string{} + } + names := make([]string, 0, len(list)) + for _, v := range list { + names = append(names, v.Name) + } + return names +} + +// secretNames contacts the API to get a list of secret names. +// In case of an error, an empty list is returned. +func secretNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string { + list, err := dockerCLI.Client().SecretList(cmd.Context(), client.SecretListOptions{}) + if err != nil { + return []string{} + } + names := make([]string, 0, len(list)) + for _, v := range list { + names = append(names, v.Spec.Name) + } + return names +} + +// serviceNames contacts the API to get a list of service names. +// In case of an error, an empty list is returned. +func serviceNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string { + list, err := dockerCLI.Client().ServiceList(cmd.Context(), client.ServiceListOptions{}) + if err != nil { + return []string{} + } + names := make([]string, 0, len(list)) + for _, v := range list { + names = append(names, v.Spec.Name) + } + return names +} + +// taskNames contacts the API to get a list of service names. +// In case of an error, an empty list is returned. +func taskNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string { + list, err := dockerCLI.Client().TaskList(cmd.Context(), client.TaskListOptions{}) + if err != nil || len(list) == 0 { + return []string{} + } + + resolver := idresolver.New(dockerCLI.Client(), false) + names := make([]string, 0, len(list)) + for _, task := range list { + serviceName, err := resolver.Resolve(cmd.Context(), swarm.Service{}, task.ServiceID) + if err != nil { + continue + } + if task.Slot != 0 { + names = append(names, fmt.Sprintf("%v.%v", serviceName, task.Slot)) + } else { + names = append(names, fmt.Sprintf("%v.%v", serviceName, task.NodeID)) + } + } + 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 { @@ -232,3 +316,39 @@ func volumeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []s } return names } + +// completeObjectNames completes names of objects based on the "--type" flag +// +// TODO(thaJeztah): completion functions in this package don't remove names that have already been completed +// this causes completion to continue even if a given name was already completed. +func completeObjectNames(dockerCLI completion.APIClientProvider) cobra.CompletionFunc { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if f := cmd.Flags().Lookup("type"); f != nil && f.Changed { + switch f.Value.String() { + case typeConfig: + return configNames(dockerCLI, cmd), cobra.ShellCompDirectiveNoFileComp + case typeContainer: + return containerNames(dockerCLI, cmd, args, toComplete), cobra.ShellCompDirectiveNoFileComp + case typeImage: + return imageNames(dockerCLI, cmd), cobra.ShellCompDirectiveNoFileComp + case typeNetwork: + return networkNames(dockerCLI, cmd), cobra.ShellCompDirectiveNoFileComp + case typeNode: + return nodeNames(dockerCLI, cmd), cobra.ShellCompDirectiveNoFileComp + case typePlugin: + return pluginNames(dockerCLI, cmd), cobra.ShellCompDirectiveNoFileComp + case typeSecret: + return secretNames(dockerCLI, cmd), cobra.ShellCompDirectiveNoFileComp + case typeService: + return serviceNames(dockerCLI, cmd), cobra.ShellCompDirectiveNoFileComp + case typeTask: + return taskNames(dockerCLI, cmd), cobra.ShellCompDirectiveNoFileComp + case typeVolume: + return volumeNames(dockerCLI, cmd), cobra.ShellCompDirectiveNoFileComp + default: + return nil, cobra.ShellCompDirectiveNoFileComp + } + } + return nil, cobra.ShellCompDirectiveNoFileComp + } +} diff --git a/cli/command/system/inspect.go b/cli/command/system/inspect.go index f248314bc..8fa11ea3f 100644 --- a/cli/command/system/inspect.go +++ b/cli/command/system/inspect.go @@ -73,7 +73,7 @@ func newInspectCommand(dockerCLI command.Cli) *cobra.Command { return runInspect(cmd.Context(), dockerCLI, opts) }, // TODO(thaJeztah): should we consider adding completion for common object-types? (images, containers?) - ValidArgsFunction: cobra.NoFileCompletions, + ValidArgsFunction: completeObjectNames(dockerCLI), DisableFlagsInUseLine: true, }