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"
"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 {

View File

@ -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)
}

View File

@ -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
}