Refactor the `stack services` command to be uniform

Running `docker stack services <STACK> --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 <djordje.lukic@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Djordje Lukic 2019-10-10 14:22:20 +02:00 committed by Sebastiaan van Stijn
parent b3cde356f6
commit 568ea3a329
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
3 changed files with 70 additions and 55 deletions

View File

@ -4,8 +4,6 @@ import (
"fmt" "fmt"
"strings" "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/cli/cli/command/stack/options"
"github.com/docker/compose-on-kubernetes/api/labels" "github.com/docker/compose-on-kubernetes/api/labels"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
@ -79,56 +77,43 @@ func getResourcesForServiceList(dockerCli *KubeCli, filters filters.Args, labelS
return replicas, daemons, services, nil return replicas, daemons, services, nil
} }
// RunServices is the kubernetes implementation of docker stack services // GetServices is the kubernetes implementation of listing stack services
func RunServices(dockerCli *KubeCli, opts options.Services) error { func GetServices(dockerCli *KubeCli, opts options.Services) ([]swarm.Service, error) {
filters := opts.Filter.Value() filters := opts.Filter.Value()
if err := filters.Validate(supportedServicesFilters); err != nil { if err := filters.Validate(supportedServicesFilters); err != nil {
return err return nil, err
} }
client, err := dockerCli.composeClient() client, err := dockerCli.composeClient()
if err != nil { if err != nil {
return nil return nil, err
} }
stacks, err := client.Stacks(false) stacks, err := client.Stacks(false)
if err != nil { if err != nil {
return nil return nil, err
} }
stackName := opts.Namespace stackName := opts.Namespace
_, err = stacks.Get(stackName) _, err = stacks.Get(stackName)
if apierrs.IsNotFound(err) { if apierrs.IsNotFound(err) {
return fmt.Errorf("nothing found in stack: %s", stackName) return []swarm.Service{}, nil
} }
if err != nil { if err != nil {
return err return nil, err
} }
labelSelector := generateLabelSelector(filters, stackName) labelSelector := generateLabelSelector(filters, stackName)
replicasList, daemonsList, servicesList, err := getResourcesForServiceList(dockerCli, filters, labelSelector) replicasList, daemonsList, servicesList, err := getResourcesForServiceList(dockerCli, filters, labelSelector)
if err != nil { if err != nil {
return err return nil, err
} }
// Convert Replicas sets and kubernetes services to swarm services and formatter information // Convert Replicas sets and kubernetes services to swarm services and formatter information
services, err := convertToServices(replicasList, daemonsList, servicesList) services, err := convertToServices(replicasList, daemonsList, servicesList)
if err != nil { if err != nil {
return err return nil, err
} }
services = filterServicesByName(services, filters.Get("name"), stackName) services = filterServicesByName(services, filters.Get("name"), stackName)
format := opts.Format return services, nil
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)
} }
func filterServicesByName(services []swarm.Service, names []string, stackName string) []swarm.Service { func filterServicesByName(services []swarm.Service, names []string, stackName string) []swarm.Service {

View File

@ -1,14 +1,21 @@
package stack package stack
import ( import (
"fmt"
"sort"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "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/kubernetes"
"github.com/docker/cli/cli/command/stack/options" "github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/command/stack/swarm" "github.com/docker/cli/cli/command/stack/swarm"
cliopts "github.com/docker/cli/opts" cliopts "github.com/docker/cli/opts"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"vbom.ml/util/sortorder"
) )
func newServicesCommand(dockerCli command.Cli, common *commonOptions) *cobra.Command { 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 // RunServices performs a stack services against the specified orchestrator
func RunServices(dockerCli command.Cli, flags *pflag.FlagSet, commonOrchestrator command.Orchestrator, opts options.Services) error { func RunServices(dockerCli command.Cli, flags *pflag.FlagSet, commonOrchestrator command.Orchestrator, opts options.Services) error {
return runOrchestratedCommand(dockerCli, flags, commonOrchestrator, services, err := GetServices(dockerCli, flags, commonOrchestrator, opts)
func() error { return swarm.RunServices(dockerCli, opts) }, if err != nil {
func(kli *kubernetes.KubeCli) error { return kubernetes.RunServices(kli, opts) }) 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)
} }

View File

@ -2,17 +2,16 @@ package swarm
import ( import (
"context" "context"
"fmt"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/service" "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/cli/cli/command/stack/options"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
) )
// RunServices is the swarm implementation of docker stack services // GetServices is the swarm implementation of listing stack services
func RunServices(dockerCli command.Cli, opts options.Services) error { func GetServices(dockerCli command.Cli, opts options.Services) ([]swarm.Service, error) {
var ( var (
err error err error
ctx = context.Background() ctx = context.Background()
@ -30,13 +29,7 @@ func RunServices(dockerCli command.Cli, opts options.Services) error {
services, err := client.ServiceList(ctx, listOpts) services, err := client.ServiceList(ctx, listOpts)
if err != nil { if err != nil {
return err return nil, 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
} }
if listOpts.Status { 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. // a ServiceStatus set, and perform a lookup for those.
services, err = service.AppendServiceStatus(ctx, client, services) services, err = service.AppendServiceStatus(ctx, client, services)
if err != nil { if err != nil {
return err return nil, err
} }
} }
return services, nil
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)
} }