From 4d947de2927ab35a734a0c816a01f54744d3d429 Mon Sep 17 00:00:00 2001 From: Mathieu Champlon Date: Wed, 25 Apr 2018 14:24:46 +0200 Subject: [PATCH] Support 'all' in orchestrator flag for docker stack ls All other docker stack commands report an error when passed this value. Signed-off-by: Mathieu Champlon --- cli/command/cli.go | 12 ++++++- cli/command/cli_test.go | 19 +++++++++++ cli/command/orchestrator.go | 6 +++- cli/command/stack/cmd.go | 4 +++ cli/command/stack/deploy.go | 8 +++-- cli/command/stack/kubernetes/list.go | 32 ++--------------- cli/command/stack/list.go | 51 +++++++++++++++++++++++----- cli/command/stack/list_test.go | 27 ++++++++------- cli/command/stack/ps.go | 8 +++-- cli/command/stack/remove.go | 8 +++-- cli/command/stack/services.go | 8 +++-- cli/command/stack/swarm/list.go | 38 +++------------------ cli/command/system/version_test.go | 4 +-- cli/flags/common.go | 2 +- internal/test/cli.go | 15 ++++++++ 15 files changed, 144 insertions(+), 98 deletions(-) diff --git a/cli/command/cli.go b/cli/command/cli.go index 344fddd61e..29c7af2129 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -244,7 +244,17 @@ type ClientInfo struct { // HasKubernetes checks if kubernetes orchestrator is enabled func (c ClientInfo) HasKubernetes() bool { - return c.HasExperimental && c.Orchestrator == OrchestratorKubernetes + return c.HasExperimental && (c.Orchestrator == OrchestratorKubernetes || c.Orchestrator == OrchestratorAll) +} + +// HasSwarm checks if swarm orchestrator is enabled +func (c ClientInfo) HasSwarm() bool { + return c.Orchestrator == OrchestratorSwarm || c.Orchestrator == OrchestratorAll +} + +// HasAll checks if all orchestrator is enabled +func (c ClientInfo) HasAll() bool { + return c.Orchestrator == OrchestratorAll } // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. diff --git a/cli/command/cli_test.go b/cli/command/cli_test.go index 36d6f098ec..e5aa0f908d 100644 --- a/cli/command/cli_test.go +++ b/cli/command/cli_test.go @@ -171,6 +171,7 @@ func TestOrchestratorSwitch(t *testing.T) { flagOrchestrator string expectedOrchestrator string expectedKubernetes bool + expectedSwarm bool }{ { doc: "default", @@ -179,6 +180,7 @@ func TestOrchestratorSwitch(t *testing.T) { }`, expectedOrchestrator: "swarm", expectedKubernetes: false, + expectedSwarm: true, }, { doc: "kubernetesIsExperimental", @@ -190,6 +192,7 @@ func TestOrchestratorSwitch(t *testing.T) { flagOrchestrator: "kubernetes", expectedOrchestrator: "swarm", expectedKubernetes: false, + expectedSwarm: true, }, { doc: "kubernetesConfigFile", @@ -199,6 +202,7 @@ func TestOrchestratorSwitch(t *testing.T) { }`, expectedOrchestrator: "kubernetes", expectedKubernetes: true, + expectedSwarm: false, }, { doc: "kubernetesEnv", @@ -208,6 +212,7 @@ func TestOrchestratorSwitch(t *testing.T) { envOrchestrator: "kubernetes", expectedOrchestrator: "kubernetes", expectedKubernetes: true, + expectedSwarm: false, }, { doc: "kubernetesFlag", @@ -217,6 +222,17 @@ func TestOrchestratorSwitch(t *testing.T) { flagOrchestrator: "kubernetes", expectedOrchestrator: "kubernetes", expectedKubernetes: true, + expectedSwarm: false, + }, + { + doc: "allOrchestratorFlag", + configfile: `{ + "experimental": "enabled" + }`, + flagOrchestrator: "all", + expectedOrchestrator: "all", + expectedKubernetes: true, + expectedSwarm: true, }, { doc: "envOverridesConfigFile", @@ -227,6 +243,7 @@ func TestOrchestratorSwitch(t *testing.T) { envOrchestrator: "swarm", expectedOrchestrator: "swarm", expectedKubernetes: false, + expectedSwarm: true, }, { doc: "flagOverridesEnv", @@ -237,6 +254,7 @@ func TestOrchestratorSwitch(t *testing.T) { flagOrchestrator: "swarm", expectedOrchestrator: "swarm", expectedKubernetes: false, + expectedSwarm: true, }, } @@ -260,6 +278,7 @@ func TestOrchestratorSwitch(t *testing.T) { err := cli.Initialize(options) assert.NilError(t, err) assert.Check(t, is.Equal(testcase.expectedKubernetes, cli.ClientInfo().HasKubernetes())) + assert.Check(t, is.Equal(testcase.expectedSwarm, cli.ClientInfo().HasSwarm())) assert.Check(t, is.Equal(testcase.expectedOrchestrator, string(cli.ClientInfo().Orchestrator))) }) } diff --git a/cli/command/orchestrator.go b/cli/command/orchestrator.go index b15c8f41bf..b8224edb18 100644 --- a/cli/command/orchestrator.go +++ b/cli/command/orchestrator.go @@ -13,6 +13,8 @@ const ( OrchestratorKubernetes = Orchestrator("kubernetes") // OrchestratorSwarm orchestrator OrchestratorSwarm = Orchestrator("swarm") + // OrchestratorAll orchestrator + OrchestratorAll = Orchestrator("all") orchestratorUnset = Orchestrator("unset") defaultOrchestrator = OrchestratorSwarm @@ -27,8 +29,10 @@ func normalize(value string) (Orchestrator, error) { return OrchestratorSwarm, nil case "": return orchestratorUnset, nil + case "all": + return OrchestratorAll, nil default: - return defaultOrchestrator, fmt.Errorf("specified orchestrator %q is invalid, please use either kubernetes or swarm", value) + return defaultOrchestrator, fmt.Errorf("specified orchestrator %q is invalid, please use either kubernetes, swarm or all", value) } } diff --git a/cli/command/stack/cmd.go b/cli/command/stack/cmd.go index 5fe0795836..5e4fc2e954 100644 --- a/cli/command/stack/cmd.go +++ b/cli/command/stack/cmd.go @@ -1,11 +1,15 @@ package stack import ( + "fmt" + "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/spf13/cobra" ) +var errUnsupportedAllOrchestrator = fmt.Errorf(`no orchestrator specified: use either "kubernetes" or "swarm"`) + // NewStackCommand returns a cobra command for `stack` subcommands func NewStackCommand(dockerCli command.Cli) *cobra.Command { cmd := &cobra.Command{ diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index a21640d054..9aedcedab5 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -19,14 +19,18 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command { Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.Namespace = args[0] - if dockerCli.ClientInfo().HasKubernetes() { + switch { + case dockerCli.ClientInfo().HasAll(): + return errUnsupportedAllOrchestrator + case dockerCli.ClientInfo().HasKubernetes(): kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) if err != nil { return err } return kubernetes.RunDeploy(kli, opts) + default: + return swarm.RunDeploy(dockerCli, opts) } - return swarm.RunDeploy(dockerCli, opts) }, } diff --git a/cli/command/stack/kubernetes/list.go b/cli/command/stack/kubernetes/list.go index cc4b3ef981..155134e348 100644 --- a/cli/command/stack/kubernetes/list.go +++ b/cli/command/stack/kubernetes/list.go @@ -1,44 +1,18 @@ package kubernetes import ( - "sort" - "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/stack/options" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "vbom.ml/util/sortorder" ) -// RunList is the kubernetes implementation of docker stack ls -func RunList(dockerCli *KubeCli, opts options.List) error { - stacks, err := getStacks(dockerCli, opts.AllNamespaces) - if err != nil { - return err - } - format := opts.Format - if format == "" || format == formatter.TableFormatKey { - format = formatter.KubernetesStackTableFormat - } - stackCtx := formatter.Context{ - Output: dockerCli.Out(), - Format: formatter.Format(format), - } - sort.Sort(byName(stacks)) - return formatter.StackWrite(stackCtx, stacks) -} - -type byName []*formatter.Stack - -func (n byName) Len() int { return len(n) } -func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] } -func (n byName) Less(i, j int) bool { return sortorder.NaturalLess(n[i].Name, n[j].Name) } - -func getStacks(kubeCli *KubeCli, allNamespaces bool) ([]*formatter.Stack, error) { +// GetStacks lists the kubernetes stacks. +func GetStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) { composeClient, err := kubeCli.composeClient() if err != nil { return nil, err } - stackSvc, err := composeClient.Stacks(allNamespaces) + stackSvc, err := composeClient.Stacks(opts.AllNamespaces) if err != nil { return nil, err } diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index fc96fd6791..ae94c4bd1f 100644 --- a/cli/command/stack/list.go +++ b/cli/command/stack/list.go @@ -1,12 +1,16 @@ package stack import ( + "sort" + "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command/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" "github.com/spf13/cobra" + "vbom.ml/util/sortorder" ) func newListCommand(dockerCli command.Cli) *cobra.Command { @@ -18,14 +22,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { Short: "List stacks", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - if dockerCli.ClientInfo().HasKubernetes() { - kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) - if err != nil { - return err - } - return kubernetes.RunList(kli, opts) - } - return swarm.RunList(dockerCli, opts) + return runList(cmd, dockerCli, opts) }, } @@ -33,5 +30,43 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { flags.StringVar(&opts.Format, "format", "", "Pretty-print stacks using a Go template") flags.BoolVarP(&opts.AllNamespaces, "all-namespaces", "", false, "List stacks among all Kubernetes namespaces") flags.SetAnnotation("all-namespaces", "kubernetes", nil) + flags.SetAnnotation("all-namespaces", "experimentalCLI", nil) return cmd } + +func runList(cmd *cobra.Command, dockerCli command.Cli, opts options.List) error { + stacks := []*formatter.Stack{} + if dockerCli.ClientInfo().HasSwarm() { + ss, err := swarm.GetStacks(dockerCli) + if err != nil { + return err + } + stacks = append(stacks, ss...) + } + if dockerCli.ClientInfo().HasKubernetes() { + kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) + if err != nil { + return err + } + ss, err := kubernetes.GetStacks(kli, opts) + if err != nil { + return err + } + stacks = append(stacks, ss...) + } + format := opts.Format + if format == "" || format == formatter.TableFormatKey { + format = formatter.SwarmStackTableFormat + if dockerCli.ClientInfo().HasKubernetes() { + format = formatter.KubernetesStackTableFormat + } + } + stackCtx := formatter.Context{ + Output: dockerCli.Out(), + Format: formatter.Format(format), + } + sort.Slice(stacks, func(i, j int) bool { + return sortorder.NaturalLess(stacks[i].Name, stacks[j].Name) + }) + return formatter.StackWrite(stackCtx, stacks) +} diff --git a/cli/command/stack/list_test.go b/cli/command/stack/list_test.go index b3a9bdc3b7..c3d827ccda 100644 --- a/cli/command/stack/list_test.go +++ b/cli/command/stack/list_test.go @@ -48,7 +48,7 @@ func TestListErrors(t *testing.T) { for _, tc := range testCases { cmd := newListCommand(test.NewFakeCli(&fakeClient{ serviceListFunc: tc.serviceListFunc, - })) + }, test.OrchestratorSwarm)) cmd.SetArgs(tc.args) cmd.SetOutput(ioutil.Discard) for key, value := range tc.flags { @@ -59,16 +59,17 @@ func TestListErrors(t *testing.T) { } func TestListWithFormat(t *testing.T) { - cli := test.NewFakeCli(&fakeClient{ - serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { - return []swarm.Service{ - *Service( - ServiceLabels(map[string]string{ - "com.docker.stack.namespace": "service-name-foo", - }), - )}, nil - }, - }) + cli := test.NewFakeCli( + &fakeClient{ + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{ + *Service( + ServiceLabels(map[string]string{ + "com.docker.stack.namespace": "service-name-foo", + }), + )}, nil + }, + }, test.OrchestratorSwarm) cmd := newListCommand(cli) cmd.Flags().Set("format", "{{ .Name }}") assert.NilError(t, cmd.Execute()) @@ -85,7 +86,7 @@ func TestListWithoutFormat(t *testing.T) { }), )}, nil }, - }) + }, test.OrchestratorSwarm) cmd := newListCommand(cli) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "stack-list-without-format.golden") @@ -138,7 +139,7 @@ func TestListOrder(t *testing.T) { serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { return uc.swarmServices, nil }, - }) + }, test.OrchestratorSwarm) cmd := newListCommand(cli) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), uc.golden) diff --git a/cli/command/stack/ps.go b/cli/command/stack/ps.go index 7c003bda80..96cb9a7a1e 100644 --- a/cli/command/stack/ps.go +++ b/cli/command/stack/ps.go @@ -19,14 +19,18 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command { Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.Namespace = args[0] - if dockerCli.ClientInfo().HasKubernetes() { + switch { + case dockerCli.ClientInfo().HasAll(): + return errUnsupportedAllOrchestrator + case dockerCli.ClientInfo().HasKubernetes(): kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) if err != nil { return err } return kubernetes.RunPS(kli, opts) + default: + return swarm.RunPS(dockerCli, opts) } - return swarm.RunPS(dockerCli, opts) }, } flags := cmd.Flags() diff --git a/cli/command/stack/remove.go b/cli/command/stack/remove.go index 25df2a5b61..00242507d7 100644 --- a/cli/command/stack/remove.go +++ b/cli/command/stack/remove.go @@ -19,14 +19,18 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command { Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.Namespaces = args - if dockerCli.ClientInfo().HasKubernetes() { + switch { + case dockerCli.ClientInfo().HasAll(): + return errUnsupportedAllOrchestrator + case dockerCli.ClientInfo().HasKubernetes(): kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) if err != nil { return err } return kubernetes.RunRemove(kli, opts) + default: + return swarm.RunRemove(dockerCli, opts) } - return swarm.RunRemove(dockerCli, opts) }, } return cmd diff --git a/cli/command/stack/services.go b/cli/command/stack/services.go index c05d3e51c1..c218d99522 100644 --- a/cli/command/stack/services.go +++ b/cli/command/stack/services.go @@ -19,14 +19,18 @@ func newServicesCommand(dockerCli command.Cli) *cobra.Command { Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.Namespace = args[0] - if dockerCli.ClientInfo().HasKubernetes() { + switch { + case dockerCli.ClientInfo().HasAll(): + return errUnsupportedAllOrchestrator + case dockerCli.ClientInfo().HasKubernetes(): kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags())) if err != nil { return err } return kubernetes.RunServices(kli, opts) + default: + return swarm.RunServices(dockerCli, opts) } - return swarm.RunServices(dockerCli, opts) }, } flags := cmd.Flags() diff --git a/cli/command/stack/swarm/list.go b/cli/command/stack/swarm/list.go index 76c1843232..c0c19d0a56 100644 --- a/cli/command/stack/swarm/list.go +++ b/cli/command/stack/swarm/list.go @@ -2,48 +2,18 @@ package swarm import ( "context" - "sort" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" - "github.com/docker/cli/cli/command/stack/options" "github.com/docker/cli/cli/compose/convert" "github.com/docker/docker/api/types" - "github.com/docker/docker/client" "github.com/pkg/errors" - "vbom.ml/util/sortorder" ) -// RunList is the swarm implementation of docker stack ls -func RunList(dockerCli command.Cli, opts options.List) error { - client := dockerCli.Client() - ctx := context.Background() - - stacks, err := getStacks(ctx, client) - if err != nil { - return err - } - format := opts.Format - if format == "" || format == formatter.TableFormatKey { - format = formatter.SwarmStackTableFormat - } - stackCtx := formatter.Context{ - Output: dockerCli.Out(), - Format: formatter.Format(format), - } - sort.Sort(byName(stacks)) - return formatter.StackWrite(stackCtx, stacks) -} - -type byName []*formatter.Stack - -func (n byName) Len() int { return len(n) } -func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] } -func (n byName) Less(i, j int) bool { return sortorder.NaturalLess(n[i].Name, n[j].Name) } - -func getStacks(ctx context.Context, apiclient client.APIClient) ([]*formatter.Stack, error) { - services, err := apiclient.ServiceList( - ctx, +// GetStacks lists the swarm stacks. +func GetStacks(dockerCli command.Cli) ([]*formatter.Stack, error) { + services, err := dockerCli.Client().ServiceList( + context.Background(), types.ServiceListOptions{Filters: getAllStacksFilter()}) if err != nil { return nil, err diff --git a/cli/command/system/version_test.go b/cli/command/system/version_test.go index ea2182563f..fccfcfb2e3 100644 --- a/cli/command/system/version_test.go +++ b/cli/command/system/version_test.go @@ -6,7 +6,6 @@ import ( "strings" "testing" - "github.com/docker/cli/cli/command" "github.com/gotestyourself/gotestyourself/assert" is "github.com/gotestyourself/gotestyourself/assert/cmp" @@ -39,8 +38,7 @@ func fakeServerVersion(_ context.Context) (types.Version, error) { } func TestVersionWithOrchestrator(t *testing.T) { - cli := test.NewFakeCli(&fakeClient{serverVersion: fakeServerVersion}) - cli.SetClientInfo(func() command.ClientInfo { return command.ClientInfo{Orchestrator: "swarm"} }) + cli := test.NewFakeCli(&fakeClient{serverVersion: fakeServerVersion}, test.OrchestratorSwarm) cmd := NewVersionCommand(cli) assert.NilError(t, cmd.Execute()) assert.Check(t, is.Contains(cleanTabs(cli.OutBuffer().String()), "Orchestrator: swarm")) diff --git a/cli/flags/common.go b/cli/flags/common.go index 6a9e79078c..9c37aed648 100644 --- a/cli/flags/common.go +++ b/cli/flags/common.go @@ -55,7 +55,7 @@ func (commonOpts *CommonOptions) InstallFlags(flags *pflag.FlagSet) { flags.StringVarP(&commonOpts.LogLevel, "log-level", "l", "info", `Set the logging level ("debug"|"info"|"warn"|"error"|"fatal")`) flags.BoolVar(&commonOpts.TLS, "tls", dockerTLS, "Use TLS; implied by --tlsverify") flags.BoolVar(&commonOpts.TLSVerify, FlagTLSVerify, dockerTLSVerify, "Use TLS and verify the remote") - flags.StringVar(&commonOpts.Orchestrator, "orchestrator", "", "Which orchestrator to use with the docker cli (swarm|kubernetes) (default swarm) (experimental)") + flags.StringVar(&commonOpts.Orchestrator, "orchestrator", "", "Orchestrator to use (swarm|kubernetes|all) (experimental)") flags.SetAnnotation("orchestrator", "experimentalCLI", nil) // TODO use flag flags.String("identity"}, "i", "", "Path to libtrust key file") diff --git a/internal/test/cli.go b/internal/test/cli.go index 17fab645ea..fdb8807294 100644 --- a/internal/test/cli.go +++ b/internal/test/cli.go @@ -167,3 +167,18 @@ func (c *FakeCli) ContentTrustEnabled() bool { func EnableContentTrust(c *FakeCli) { c.contentTrust = true } + +// OrchestratorSwarm sets a command.ClientInfo with Swarm orchestrator +func OrchestratorSwarm(c *FakeCli) { + c.SetClientInfo(func() command.ClientInfo { return command.ClientInfo{Orchestrator: "swarm"} }) +} + +// OrchestratorKubernetes sets a command.ClientInfo with Kubernetes orchestrator +func OrchestratorKubernetes(c *FakeCli) { + c.SetClientInfo(func() command.ClientInfo { return command.ClientInfo{Orchestrator: "kubernetes"} }) +} + +// OrchestratorAll sets a command.ClientInfo with all orchestrator +func OrchestratorAll(c *FakeCli) { + c.SetClientInfo(func() command.ClientInfo { return command.ClientInfo{Orchestrator: "all"} }) +}