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 <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2024-06-04 10:56:26 +02:00
parent ce85b24440
commit 20d1b661bc
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
7 changed files with 35 additions and 24 deletions

View File

@ -3,23 +3,33 @@ package completion
import ( import (
"os" "os"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume" "github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// ValidArgsFn a function to be used by cobra command as `ValidArgsFunction` to offer command line completion // 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) 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 // 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) { 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 { if err != nil {
return nil, cobra.ShellCompDirectiveError return nil, cobra.ShellCompDirectiveError
} }
@ -34,9 +44,9 @@ func ImageNames(dockerCli command.Cli) ValidArgsFn {
// ContainerNames offers completion for container names and IDs // ContainerNames offers completion for container names and IDs
// By default, only names are returned. // By default, only names are returned.
// Set DOCKER_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs. // 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) { 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, All: all,
}) })
if err != nil { if err != nil {
@ -67,9 +77,9 @@ func ContainerNames(dockerCli command.Cli, all bool, filters ...func(types.Conta
} }
// VolumeNames offers completion for volumes // 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) { 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 { if err != nil {
return nil, cobra.ShellCompDirectiveError return nil, cobra.ShellCompDirectiveError
} }
@ -82,9 +92,9 @@ func VolumeNames(dockerCli command.Cli) ValidArgsFn {
} }
// NetworkNames offers completion for networks // 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) { 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 { if err != nil {
return nil, cobra.ShellCompDirectiveError return nil, cobra.ShellCompDirectiveError
} }

View File

@ -30,9 +30,9 @@ func NewConfigCommand(dockerCli command.Cli) *cobra.Command {
} }
// completeNames offers completion for swarm configs // 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) { 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 { if err != nil {
return nil, cobra.ShellCompDirectiveError return nil, cobra.ShellCompDirectiveError
} }

View File

@ -30,9 +30,9 @@ func NewSecretCommand(dockerCli command.Cli) *cobra.Command {
} }
// completeNames offers completion for swarm secrets // 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) { 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 { if err != nil {
return nil, cobra.ShellCompDirectiveError return nil, cobra.ShellCompDirectiveError
} }

View File

@ -35,9 +35,9 @@ func NewServiceCommand(dockerCli command.Cli) *cobra.Command {
} }
// CompletionFn offers completion for swarm services // 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) { 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 { if err != nil {
return nil, cobra.ShellCompDirectiveError return nil, cobra.ShellCompDirectiveError
} }

View File

@ -46,9 +46,9 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command {
} }
// completeNames offers completion for swarm stacks // 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) { 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 { if err != nil {
return nil, cobra.ShellCompDirectiveError return nil, cobra.ShellCompDirectiveError
} }

View File

@ -2,6 +2,7 @@ package stack
import ( import (
"context" "context"
"io"
"sort" "sort"
"github.com/docker/cli/cli" "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 // RunList performs a stack list against the specified swarm cluster
func RunList(ctx context.Context, dockerCli command.Cli, opts options.List) error { 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 { if err != nil {
return err return err
} }
stacks := make([]*formatter.Stack, 0, len(ss)) stacks := make([]*formatter.Stack, 0, len(ss))
stacks = append(stacks, 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) fmt := formatter.Format(opts.Format)
if fmt == "" || fmt == formatter.TableFormatKey { if fmt == "" || fmt == formatter.TableFormatKey {
fmt = formatter.SwarmStackTableFormat fmt = formatter.SwarmStackTableFormat
} }
stackCtx := formatter.Context{ stackCtx := formatter.Context{
Output: dockerCli.Out(), Output: out,
Format: fmt, Format: fmt,
} }
sort.Slice(stacks, func(i, j int) bool { sort.Slice(stacks, func(i, j int) bool {

View File

@ -3,16 +3,16 @@ package swarm
import ( import (
"context" "context"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/command/stack/formatter"
"github.com/docker/cli/cli/compose/convert" "github.com/docker/cli/cli/compose/convert"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// GetStacks lists the swarm stacks. // GetStacks lists the swarm stacks.
func GetStacks(ctx context.Context, dockerCli command.Cli) ([]*formatter.Stack, error) { func GetStacks(ctx context.Context, apiClient client.ServiceAPIClient) ([]*formatter.Stack, error) {
services, err := dockerCli.Client().ServiceList( services, err := apiClient.ServiceList(
ctx, ctx,
types.ServiceListOptions{Filters: getAllStacksFilter()}) types.ServiceListOptions{Filters: getAllStacksFilter()})
if err != nil { if err != nil {