From 20d1b661bccbe8d3c5c5651faac92ffd8490b964 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 4 Jun 2024 10:56:26 +0200 Subject: [PATCH] cli/command: use shallower interface for completions The completion functions only need the API-client, and not all of the CLI. However, passing the API-client as argument would mean that the API-client is initialized early, which may not be what we want, so instead, defining an APIClientProvider interface to preserve the behavior of initializing when needed only. While updating, also simplify stack.format to only require an io.Writer. Signed-off-by: Sebastiaan van Stijn --- cli/command/completion/functions.go | 28 +++++++++++++++++++--------- cli/command/config/cmd.go | 4 ++-- cli/command/secret/cmd.go | 4 ++-- cli/command/service/cmd.go | 4 ++-- cli/command/stack/cmd.go | 4 ++-- cli/command/stack/list.go | 9 +++++---- cli/command/stack/swarm/list.go | 6 +++--- 7 files changed, 35 insertions(+), 24 deletions(-) diff --git a/cli/command/completion/functions.go b/cli/command/completion/functions.go index 7cebeac921..6026ef7692 100644 --- a/cli/command/completion/functions.go +++ b/cli/command/completion/functions.go @@ -3,23 +3,33 @@ package completion import ( "os" - "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "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/client" "github.com/spf13/cobra" ) // ValidArgsFn a function to be used by cobra command as `ValidArgsFunction` to offer command line completion type ValidArgsFn func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) +// APIClientProvider provides a method to get an [client.APIClient], initializing +// it if needed. +// +// It's a smaller interface than [command.Cli], and used in situations where an +// APIClient is needed, but we want to postpone initializing the client until +// it's used. +type APIClientProvider interface { + Client() client.APIClient +} + // ImageNames offers completion for images present within the local store -func ImageNames(dockerCli command.Cli) ValidArgsFn { +func ImageNames(dockerCLI APIClientProvider) ValidArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list, err := dockerCli.Client().ImageList(cmd.Context(), image.ListOptions{}) + list, err := dockerCLI.Client().ImageList(cmd.Context(), image.ListOptions{}) if err != nil { return nil, cobra.ShellCompDirectiveError } @@ -34,9 +44,9 @@ func ImageNames(dockerCli command.Cli) ValidArgsFn { // ContainerNames offers completion for container names and IDs // By default, only names are returned. // Set DOCKER_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs. -func ContainerNames(dockerCli command.Cli, all bool, filters ...func(types.Container) bool) ValidArgsFn { +func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(types.Container) bool) ValidArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list, err := dockerCli.Client().ContainerList(cmd.Context(), container.ListOptions{ + list, err := dockerCLI.Client().ContainerList(cmd.Context(), container.ListOptions{ All: all, }) if err != nil { @@ -67,9 +77,9 @@ func ContainerNames(dockerCli command.Cli, all bool, filters ...func(types.Conta } // VolumeNames offers completion for volumes -func VolumeNames(dockerCli command.Cli) ValidArgsFn { +func VolumeNames(dockerCLI APIClientProvider) ValidArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list, err := dockerCli.Client().VolumeList(cmd.Context(), volume.ListOptions{}) + list, err := dockerCLI.Client().VolumeList(cmd.Context(), volume.ListOptions{}) if err != nil { return nil, cobra.ShellCompDirectiveError } @@ -82,9 +92,9 @@ func VolumeNames(dockerCli command.Cli) ValidArgsFn { } // NetworkNames offers completion for networks -func NetworkNames(dockerCli command.Cli) ValidArgsFn { +func NetworkNames(dockerCLI APIClientProvider) ValidArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list, err := dockerCli.Client().NetworkList(cmd.Context(), network.ListOptions{}) + list, err := dockerCLI.Client().NetworkList(cmd.Context(), network.ListOptions{}) if err != nil { return nil, cobra.ShellCompDirectiveError } diff --git a/cli/command/config/cmd.go b/cli/command/config/cmd.go index 86ad1cc09c..b8e84037cb 100644 --- a/cli/command/config/cmd.go +++ b/cli/command/config/cmd.go @@ -30,9 +30,9 @@ func NewConfigCommand(dockerCli command.Cli) *cobra.Command { } // completeNames offers completion for swarm configs -func completeNames(dockerCli command.Cli) completion.ValidArgsFn { +func completeNames(dockerCLI completion.APIClientProvider) completion.ValidArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list, err := dockerCli.Client().ConfigList(cmd.Context(), types.ConfigListOptions{}) + list, err := dockerCLI.Client().ConfigList(cmd.Context(), types.ConfigListOptions{}) if err != nil { return nil, cobra.ShellCompDirectiveError } diff --git a/cli/command/secret/cmd.go b/cli/command/secret/cmd.go index ec0643b054..4b5dc26332 100644 --- a/cli/command/secret/cmd.go +++ b/cli/command/secret/cmd.go @@ -30,9 +30,9 @@ func NewSecretCommand(dockerCli command.Cli) *cobra.Command { } // completeNames offers completion for swarm secrets -func completeNames(dockerCli command.Cli) completion.ValidArgsFn { +func completeNames(dockerCLI completion.APIClientProvider) completion.ValidArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list, err := dockerCli.Client().SecretList(cmd.Context(), types.SecretListOptions{}) + list, err := dockerCLI.Client().SecretList(cmd.Context(), types.SecretListOptions{}) if err != nil { return nil, cobra.ShellCompDirectiveError } diff --git a/cli/command/service/cmd.go b/cli/command/service/cmd.go index 2c0b8fca9d..aa54d54e11 100644 --- a/cli/command/service/cmd.go +++ b/cli/command/service/cmd.go @@ -35,9 +35,9 @@ func NewServiceCommand(dockerCli command.Cli) *cobra.Command { } // CompletionFn offers completion for swarm services -func CompletionFn(dockerCli command.Cli) completion.ValidArgsFn { +func CompletionFn(dockerCLI completion.APIClientProvider) completion.ValidArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list, err := dockerCli.Client().ServiceList(cmd.Context(), types.ServiceListOptions{}) + list, err := dockerCLI.Client().ServiceList(cmd.Context(), types.ServiceListOptions{}) if err != nil { return nil, cobra.ShellCompDirectiveError } diff --git a/cli/command/stack/cmd.go b/cli/command/stack/cmd.go index bf4dc79955..dba4e2929d 100644 --- a/cli/command/stack/cmd.go +++ b/cli/command/stack/cmd.go @@ -46,9 +46,9 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command { } // completeNames offers completion for swarm stacks -func completeNames(dockerCli command.Cli) completion.ValidArgsFn { +func completeNames(dockerCLI completion.APIClientProvider) completion.ValidArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - list, err := swarm.GetStacks(cmd.Context(), dockerCli) + list, err := swarm.GetStacks(cmd.Context(), dockerCLI.Client()) if err != nil { return nil, cobra.ShellCompDirectiveError } diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index d737b8771e..d440d95839 100644 --- a/cli/command/stack/list.go +++ b/cli/command/stack/list.go @@ -2,6 +2,7 @@ package stack import ( "context" + "io" "sort" "github.com/docker/cli/cli" @@ -36,22 +37,22 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { // RunList performs a stack list against the specified swarm cluster func RunList(ctx context.Context, dockerCli command.Cli, opts options.List) error { - ss, err := swarm.GetStacks(ctx, dockerCli) + ss, err := swarm.GetStacks(ctx, dockerCli.Client()) if err != nil { return err } stacks := make([]*formatter.Stack, 0, len(ss)) stacks = append(stacks, ss...) - return format(dockerCli, opts, stacks) + return format(dockerCli.Out(), opts, stacks) } -func format(dockerCli command.Cli, opts options.List, stacks []*formatter.Stack) error { +func format(out io.Writer, opts options.List, stacks []*formatter.Stack) error { fmt := formatter.Format(opts.Format) if fmt == "" || fmt == formatter.TableFormatKey { fmt = formatter.SwarmStackTableFormat } stackCtx := formatter.Context{ - Output: dockerCli.Out(), + Output: out, Format: fmt, } sort.Slice(stacks, func(i, j int) bool { diff --git a/cli/command/stack/swarm/list.go b/cli/command/stack/swarm/list.go index 983ea5e201..4c58b3de93 100644 --- a/cli/command/stack/swarm/list.go +++ b/cli/command/stack/swarm/list.go @@ -3,16 +3,16 @@ package swarm import ( "context" - "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/compose/convert" "github.com/docker/docker/api/types" + "github.com/docker/docker/client" "github.com/pkg/errors" ) // GetStacks lists the swarm stacks. -func GetStacks(ctx context.Context, dockerCli command.Cli) ([]*formatter.Stack, error) { - services, err := dockerCli.Client().ServiceList( +func GetStacks(ctx context.Context, apiClient client.ServiceAPIClient) ([]*formatter.Stack, error) { + services, err := apiClient.ServiceList( ctx, types.ServiceListOptions{Filters: getAllStacksFilter()}) if err != nil {