From 568ea3a3290f12ea32648a53d93b9facd0f14a14 Mon Sep 17 00:00:00 2001 From: Djordje Lukic Date: Thu, 10 Oct 2019 14:22:20 +0200 Subject: [PATCH] Refactor the `stack services` command to be uniform Running `docker stack services --orchestrator swarm would yield the message "Noting found in stack: asdf" with an exit code 0. The same command with kubernetes orchestrator would yield "nothing found in stack: adsf" (note the lower-case "nothing") and a non-zero exit code. This change makes the `stack services` command uniform for both orchestrators. The logic of getting and printing services is split to reuse the same formatting code. Signed-off-by: Djordje Lukic Signed-off-by: Sebastiaan van Stijn --- cli/command/stack/kubernetes/services.go | 35 +++++---------- cli/command/stack/services.go | 57 ++++++++++++++++++++++-- cli/command/stack/swarm/services.go | 33 +++----------- 3 files changed, 70 insertions(+), 55 deletions(-) diff --git a/cli/command/stack/kubernetes/services.go b/cli/command/stack/kubernetes/services.go index 952c439b16..3a3bb63c34 100644 --- a/cli/command/stack/kubernetes/services.go +++ b/cli/command/stack/kubernetes/services.go @@ -4,8 +4,6 @@ import ( "fmt" "strings" - "github.com/docker/cli/cli/command/service" - "github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/command/stack/options" "github.com/docker/compose-on-kubernetes/api/labels" "github.com/docker/docker/api/types/filters" @@ -79,56 +77,43 @@ func getResourcesForServiceList(dockerCli *KubeCli, filters filters.Args, labelS return replicas, daemons, services, nil } -// RunServices is the kubernetes implementation of docker stack services -func RunServices(dockerCli *KubeCli, opts options.Services) error { +// GetServices is the kubernetes implementation of listing stack services +func GetServices(dockerCli *KubeCli, opts options.Services) ([]swarm.Service, error) { filters := opts.Filter.Value() if err := filters.Validate(supportedServicesFilters); err != nil { - return err + return nil, err } client, err := dockerCli.composeClient() if err != nil { - return nil + return nil, err } stacks, err := client.Stacks(false) if err != nil { - return nil + return nil, err } stackName := opts.Namespace _, err = stacks.Get(stackName) if apierrs.IsNotFound(err) { - return fmt.Errorf("nothing found in stack: %s", stackName) + return []swarm.Service{}, nil } if err != nil { - return err + return nil, err } labelSelector := generateLabelSelector(filters, stackName) replicasList, daemonsList, servicesList, err := getResourcesForServiceList(dockerCli, filters, labelSelector) if err != nil { - return err + return nil, err } // Convert Replicas sets and kubernetes services to swarm services and formatter information services, err := convertToServices(replicasList, daemonsList, servicesList) if err != nil { - return err + return nil, err } services = filterServicesByName(services, filters.Get("name"), stackName) - format := opts.Format - if len(format) == 0 { - if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.Quiet { - format = dockerCli.ConfigFile().ServicesFormat - } else { - format = formatter.TableFormatKey - } - } - - servicesCtx := formatter.Context{ - Output: dockerCli.Out(), - Format: service.NewListFormat(format, opts.Quiet), - } - return service.ListFormatWrite(servicesCtx, services) + return services, nil } func filterServicesByName(services []swarm.Service, names []string, stackName string) []swarm.Service { diff --git a/cli/command/stack/services.go b/cli/command/stack/services.go index d875fa73a2..ffcec6511a 100644 --- a/cli/command/stack/services.go +++ b/cli/command/stack/services.go @@ -1,14 +1,21 @@ package stack import ( + "fmt" + "sort" + "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command/service" + "github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/command/stack/kubernetes" "github.com/docker/cli/cli/command/stack/options" "github.com/docker/cli/cli/command/stack/swarm" cliopts "github.com/docker/cli/opts" + swarmtypes "github.com/docker/docker/api/types/swarm" "github.com/spf13/cobra" "github.com/spf13/pflag" + "vbom.ml/util/sortorder" ) func newServicesCommand(dockerCli command.Cli, common *commonOptions) *cobra.Command { @@ -36,7 +43,51 @@ func newServicesCommand(dockerCli command.Cli, common *commonOptions) *cobra.Com // RunServices performs a stack services against the specified orchestrator func RunServices(dockerCli command.Cli, flags *pflag.FlagSet, commonOrchestrator command.Orchestrator, opts options.Services) error { - return runOrchestratedCommand(dockerCli, flags, commonOrchestrator, - func() error { return swarm.RunServices(dockerCli, opts) }, - func(kli *kubernetes.KubeCli) error { return kubernetes.RunServices(kli, opts) }) + services, err := GetServices(dockerCli, flags, commonOrchestrator, opts) + if err != nil { + return err + } + return formatWrite(dockerCli, services, opts) +} + +// GetServices returns the services for the specified orchestrator +func GetServices(dockerCli command.Cli, flags *pflag.FlagSet, commonOrchestrator command.Orchestrator, opts options.Services) ([]swarmtypes.Service, error) { + switch { + case commonOrchestrator.HasAll(): + return nil, errUnsupportedAllOrchestrator + case commonOrchestrator.HasKubernetes(): + kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(flags, commonOrchestrator)) + if err != nil { + return nil, err + } + return kubernetes.GetServices(kli, opts) + default: + return swarm.GetServices(dockerCli, opts) + } +} + +func formatWrite(dockerCli command.Cli, services []swarmtypes.Service, opts options.Services) error { + // if no services in the stack, print message and exit 0 + if len(services) == 0 { + _, _ = fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", opts.Namespace) + return nil + } + sort.Slice(services, func(i, j int) bool { + return sortorder.NaturalLess(services[i].Spec.Name, services[j].Spec.Name) + }) + + format := opts.Format + if len(format) == 0 { + if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.Quiet { + format = dockerCli.ConfigFile().ServicesFormat + } else { + format = formatter.TableFormatKey + } + } + + servicesCtx := formatter.Context{ + Output: dockerCli.Out(), + Format: service.NewListFormat(format, opts.Quiet), + } + return service.ListFormatWrite(servicesCtx, services) } diff --git a/cli/command/stack/swarm/services.go b/cli/command/stack/swarm/services.go index faa54c78da..3c24bf72dd 100644 --- a/cli/command/stack/swarm/services.go +++ b/cli/command/stack/swarm/services.go @@ -2,17 +2,16 @@ package swarm import ( "context" - "fmt" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/service" - "github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/command/stack/options" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" ) -// RunServices is the swarm implementation of docker stack services -func RunServices(dockerCli command.Cli, opts options.Services) error { +// GetServices is the swarm implementation of listing stack services +func GetServices(dockerCli command.Cli, opts options.Services) ([]swarm.Service, error) { var ( err error ctx = context.Background() @@ -30,13 +29,7 @@ func RunServices(dockerCli command.Cli, opts options.Services) error { services, err := client.ServiceList(ctx, listOpts) if err != nil { - return err - } - - // if no services in this stack, print message and exit 0 - if len(services) == 0 { - _, _ = fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", opts.Namespace) - return nil + return nil, err } if listOpts.Status { @@ -54,22 +47,8 @@ func RunServices(dockerCli command.Cli, opts options.Services) error { // a ServiceStatus set, and perform a lookup for those. services, err = service.AppendServiceStatus(ctx, client, services) if err != nil { - return err + return nil, err } } - - format := opts.Format - if len(format) == 0 { - if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.Quiet { - format = dockerCli.ConfigFile().ServicesFormat - } else { - format = formatter.TableFormatKey - } - } - - servicesCtx := formatter.Context{ - Output: dockerCli.Out(), - Format: service.NewListFormat(format, opts.Quiet), - } - return service.ListFormatWrite(servicesCtx, services) + return services, nil }